From 07a802d8fa93460c9f2a7f42da7a1f14a893a322 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 3 May 2026 23:33:56 +0200 Subject: Implement search providers --- .../JellyfinQueryHelperExtensions.cs | 100 +++++++++++++++++++-- 1 file changed, 93 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs index f386e882e2..e366bdb095 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs @@ -111,6 +111,92 @@ public static class JellyfinQueryHelperExtensions && val.map.ItemId == item.Id) == EF.Constant(!invert); } + /// + /// Filters items that match any of the specified (provider name, value) pairs. + /// + /// The source query. + /// Dictionary mapping provider names to arrays of values to match. + /// A filtered query. + public static IQueryable WhereHasAnyProviderIds( + this IQueryable baseQuery, + IReadOnlyDictionary providerIds) + { + var providerKeys = providerIds + .SelectMany(kvp => kvp.Value.Select(v => $"{kvp.Key}:{v}")) + .ToList(); + + if (providerKeys.Count == 0) + { + return baseQuery; + } + + return baseQuery.Where(e => e.Provider!.Any(p => providerKeys.Contains(p.ProviderId + ":" + p.ProviderValue))); + } + + /// + /// Filters items that have any of the specified providers. Empty/null values match any value for that provider. + /// + /// The source query. + /// Dictionary mapping provider names to optional values. + /// A filtered query. + public static IQueryable WhereHasAnyProviderId( + this IQueryable baseQuery, + IReadOnlyDictionary providerIds) + { + var existenceOnly = providerIds + .Where(e => string.IsNullOrEmpty(e.Value)) + .Select(e => e.Key) + .ToList(); + + var specificValues = providerIds + .Where(e => !string.IsNullOrEmpty(e.Value)) + .Select(e => $"{e.Key}:{e.Value}") + .ToList(); + + if (existenceOnly.Count == 0 && specificValues.Count == 0) + { + return baseQuery; + } + + if (existenceOnly.Count == 0) + { + return baseQuery.Where(e => e.Provider!.Any(p => + specificValues.Contains(p.ProviderId + ":" + p.ProviderValue))); + } + + if (specificValues.Count == 0) + { + return baseQuery.Where(e => e.Provider!.Any(p => existenceOnly.Contains(p.ProviderId))); + } + + // Single EXISTS over Provider with both predicates OR'd, instead of two separate subqueries. + return baseQuery.Where(e => e.Provider!.Any(p => + existenceOnly.Contains(p.ProviderId) || + specificValues.Contains(p.ProviderId + ":" + p.ProviderValue))); + } + + /// + /// Excludes items that match any of the specified (provider name, value) pairs. + /// + /// The source query. + /// Dictionary mapping provider names to values to exclude. + /// A filtered query. + public static IQueryable WhereExcludeProviderIds( + this IQueryable baseQuery, + IReadOnlyDictionary providerIds) + { + var excludeKeys = providerIds + .Select(e => $"{e.Key}:{e.Value}") + .ToList(); + + if (excludeKeys.Count == 0) + { + return baseQuery; + } + + return baseQuery.Where(e => e.Provider!.All(p => !excludeKeys.Contains(p.ProviderId + ":" + p.ProviderValue))); + } + /// /// Builds an optimised query expression checking one property against a list of values while maintaining an optimal query. /// @@ -138,13 +224,13 @@ public static class JellyfinQueryHelperExtensions var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericCache.MakeGenericMethod(key)); - if (oneOf.Count < 4) // arbitrary value choosen. - { - // if we have 3 or fewer values to check against its faster to do a IN(const,const,const) lookup - return Expression.Lambda>(Expression.Call(null, containsMethodInfo, Expression.Constant(oneOf), property.Body), parameter); - } - - return Expression.Lambda>(Expression.Call(null, containsMethodInfo, Expression.Call(null, _efParameterInstruction.MakeGenericMethod(oneOf.GetType()), Expression.Constant(oneOf)), property.Body), parameter); + return Expression.Lambda>( + Expression.Call( + null, + containsMethodInfo, + Expression.Call(null, _efParameterInstruction.MakeGenericMethod(oneOf.GetType()), Expression.Constant(oneOf)), + property.Body), + parameter); } internal static class ParameterReplacer -- cgit v1.2.3