< Summary - Jellyfin

Information
Class: Jellyfin.Database.Implementations.JellyfinQueryHelperExtensions
Assembly: Jellyfin.Database.Implementations
File(s): /srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs
Line coverage
35%
Covered lines: 29
Uncovered lines: 53
Coverable lines: 82
Total lines: 298
Line coverage: 35.3%
Branch coverage
29%
Covered branches: 7
Total branches: 24
Branch coverage: 29.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/2/2026 - 12:12:50 AM Line coverage: 58.1% (25/43) Branch coverage: 90% (9/10) Total lines: 1946/8/2026 - 12:16:15 AM Line coverage: 32.8% (24/73) Branch coverage: 36.3% (8/22) Total lines: 2817/27/2026 - 12:16:14 AM Line coverage: 35.3% (29/82) Branch coverage: 29.1% (7/24) Total lines: 298 5/2/2026 - 12:12:50 AM Line coverage: 58.1% (25/43) Branch coverage: 90% (9/10) Total lines: 1946/8/2026 - 12:16:15 AM Line coverage: 32.8% (24/73) Branch coverage: 36.3% (8/22) Total lines: 2817/27/2026 - 12:16:14 AM Line coverage: 35.3% (29/82) Branch coverage: 29.1% (7/24) Total lines: 298

Coverage delta

Coverage delta 54 -54

Metrics

File(s)

/srv/git/jellyfin/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinQueryHelperExtensions.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Linq.Expressions;
 8using System.Reflection;
 9using Jellyfin.Database.Implementations.Entities;
 10using Microsoft.EntityFrameworkCore;
 11
 12namespace Jellyfin.Database.Implementations;
 13
 14/// <summary>
 15/// Contains a number of query related extensions.
 16/// </summary>
 17public static class JellyfinQueryHelperExtensions
 18{
 219    private static readonly MethodInfo _containsMethodGenericCache = typeof(Enumerable).GetMethods(BindingFlags.Public |
 220    private static readonly MethodInfo _efParameterInstruction = typeof(EF).GetMethod(nameof(EF.Parameter), BindingFlags
 221    private static readonly ConcurrentDictionary<Type, MethodInfo> _containsQueryCache = new();
 22
 23    /// <summary>
 24    /// Builds an optimised query checking one property against a list of values while maintaining an optimal query.
 25    /// </summary>
 26    /// <typeparam name="TEntity">The entity.</typeparam>
 27    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 28    /// <param name="query">The source query.</param>
 29    /// <param name="oneOf">The list of items to check.</param>
 30    /// <param name="property">Property expression.</param>
 31    /// <returns>A Query.</returns>
 32    public static IQueryable<TEntity> WhereOneOrMany<TEntity, TProperty>(this IQueryable<TEntity> query, IList<TProperty
 33    {
 49034        return query.Where(OneOrManyExpressionBuilder(oneOf, property));
 35    }
 36
 37    /// <summary>
 38    /// Builds a query that checks referenced ItemValues for a cross BaseItem lookup.
 39    /// </summary>
 40    /// <param name="baseQuery">The source query.</param>
 41    /// <param name="context">The database context.</param>
 42    /// <param name="itemValueType">The type of item value to reference.</param>
 43    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 44    /// <param name="invert">If set an exclusion check is performed instead.</param>
 45    /// <returns>A Query.</returns>
 46    public static IQueryable<BaseItemEntity> WhereReferencedItem(
 47        this IQueryable<BaseItemEntity> baseQuery,
 48        JellyfinDbContext context,
 49        ItemValueType itemValueType,
 50        IList<Guid> referenceIds,
 51        bool invert = false)
 52    {
 053        return baseQuery.Where(ReferencedItemFilterExpressionBuilder(context, itemValueType, referenceIds, invert));
 54    }
 55
 56    /// <summary>
 57    /// Builds a query that checks referenced ItemValues for a cross BaseItem lookup.
 58    /// </summary>
 59    /// <param name="baseQuery">The source query.</param>
 60    /// <param name="context">The database context.</param>
 61    /// <param name="itemValueTypes">The type of item value to reference.</param>
 62    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 63    /// <param name="invert">If set an exclusion check is performed instead.</param>
 64    /// <returns>A Query.</returns>
 65    public static IQueryable<BaseItemEntity> WhereReferencedItemMultipleTypes(
 66        this IQueryable<BaseItemEntity> baseQuery,
 67        JellyfinDbContext context,
 68        IList<ItemValueType> itemValueTypes,
 69        IList<Guid> referenceIds,
 70        bool invert = false)
 71    {
 072        var itemFilter = OneOrManyExpressionBuilder<BaseItemEntity, Guid>(referenceIds, f => f.Id);
 073        var typeFilter = OneOrManyExpressionBuilder<ItemValueMap, ItemValueType>(itemValueTypes, m => m.ItemValue.Type);
 74
 75        // Flat sub-selects + Contains instead of a nested correlated .Any(...Any(...)).
 076        var referencedCleanValues = context.BaseItems
 077            .Where(itemFilter)
 078            .Select(e => e.CleanName);
 79
 080        var matchingItemIds = context.ItemValuesMap
 081            .Where(typeFilter)
 082            .Where(m => referencedCleanValues.Contains(m.ItemValue.CleanValue))
 083            .Select(m => m.ItemId);
 84
 085        if (invert)
 86        {
 087            return baseQuery.Where(e => !matchingItemIds.Contains(e.Id));
 88        }
 89
 090        return baseQuery.Where(e => matchingItemIds.Contains(e.Id));
 91    }
 92
 93    /// <summary>
 94    /// Builds a query expression that checks referenced ItemValues for a cross BaseItem lookup.
 95    /// </summary>
 96    /// <param name="context">The database context.</param>
 97    /// <param name="itemValueType">The type of item value to reference.</param>
 98    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 99    /// <param name="invert">If set an exclusion check is performed instead.</param>
 100    /// <returns>A Query.</returns>
 101    public static Expression<Func<BaseItemEntity, bool>> ReferencedItemFilterExpressionBuilder(
 102        this JellyfinDbContext context,
 103        ItemValueType itemValueType,
 104        IList<Guid> referenceIds,
 105        bool invert = false)
 106    {
 107        // Well genre/artist/album etc items do not actually set the ItemValue of thier specitic types so we cannot matc
 108        /*
 109        "(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@Ge
 110        */
 111
 0112        var itemFilter = OneOrManyExpressionBuilder<BaseItemEntity, Guid>(referenceIds, f => f.Id);
 113
 114        // Flat sub-selects + Contains instead of a nested correlated .Any(...Any(...)).
 0115        var referencedCleanValues = context.BaseItems
 0116            .Where(itemFilter)
 0117            .Select(e => e.CleanName);
 118
 0119        var matchingItemIds = context.ItemValuesMap
 0120            .Where(m => m.ItemValue.Type == itemValueType && referencedCleanValues.Contains(m.ItemValue.CleanValue))
 0121            .Select(m => m.ItemId);
 122
 0123        if (invert)
 124        {
 0125            return item => !matchingItemIds.Contains(item.Id);
 126        }
 127
 0128        return item => matchingItemIds.Contains(item.Id);
 129    }
 130
 131    /// <summary>
 132    /// Filters items that match any of the specified (provider name, value) pairs.
 133    /// </summary>
 134    /// <param name="baseQuery">The source query.</param>
 135    /// <param name="providerIds">Dictionary mapping provider names to arrays of values to match.</param>
 136    /// <returns>A filtered query.</returns>
 137    public static IQueryable<BaseItemEntity> WhereHasAnyProviderIds(
 138        this IQueryable<BaseItemEntity> baseQuery,
 139        IReadOnlyDictionary<string, string[]> providerIds)
 140    {
 0141        var providerKeys = providerIds
 0142            .SelectMany(kvp => kvp.Value.Select(v => $"{kvp.Key}:{v}"))
 0143            .ToList();
 144
 0145        if (providerKeys.Count == 0)
 146        {
 0147            return baseQuery;
 148        }
 149
 0150        return baseQuery.Where(e => e.Provider!.Any(p => providerKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
 151    }
 152
 153    /// <summary>
 154    /// Filters items that have any of the specified providers. Empty/null values match any value for that provider.
 155    /// </summary>
 156    /// <param name="baseQuery">The source query.</param>
 157    /// <param name="providerIds">Dictionary mapping provider names to optional values.</param>
 158    /// <returns>A filtered query.</returns>
 159    public static IQueryable<BaseItemEntity> WhereHasAnyProviderId(
 160        this IQueryable<BaseItemEntity> baseQuery,
 161        IReadOnlyDictionary<string, string> providerIds)
 162    {
 0163        var existenceOnly = providerIds
 0164            .Where(e => string.IsNullOrEmpty(e.Value))
 0165            .Select(e => e.Key)
 0166            .ToList();
 167
 0168        var specificValues = providerIds
 0169            .Where(e => !string.IsNullOrEmpty(e.Value))
 0170            .Select(e => $"{e.Key}:{e.Value}")
 0171            .ToList();
 172
 0173        if (existenceOnly.Count == 0 && specificValues.Count == 0)
 174        {
 0175            return baseQuery;
 176        }
 177
 0178        if (existenceOnly.Count == 0)
 179        {
 0180            return baseQuery.Where(e => e.Provider!.Any(p =>
 0181                specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
 182        }
 183
 0184        if (specificValues.Count == 0)
 185        {
 0186            return baseQuery.Where(e => e.Provider!.Any(p => existenceOnly.Contains(p.ProviderId)));
 187        }
 188
 189        // Single EXISTS over Provider with both predicates OR'd, instead of two separate subqueries.
 0190        return baseQuery.Where(e => e.Provider!.Any(p =>
 0191            existenceOnly.Contains(p.ProviderId) ||
 0192            specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
 193    }
 194
 195    /// <summary>
 196    /// Excludes items that match any of the specified (provider name, value) pairs.
 197    /// </summary>
 198    /// <param name="baseQuery">The source query.</param>
 199    /// <param name="providerIds">Dictionary mapping provider names to values to exclude.</param>
 200    /// <returns>A filtered query.</returns>
 201    public static IQueryable<BaseItemEntity> WhereExcludeProviderIds(
 202        this IQueryable<BaseItemEntity> baseQuery,
 203        IReadOnlyDictionary<string, string> providerIds)
 204    {
 0205        var excludeKeys = providerIds
 0206            .Select(e => $"{e.Key}:{e.Value}")
 0207            .ToList();
 208
 0209        if (excludeKeys.Count == 0)
 210        {
 0211            return baseQuery;
 212        }
 213
 0214        return baseQuery.Where(e => e.Provider!.All(p => !excludeKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
 215    }
 216
 217    /// <summary>
 218    /// Builds an optimised query expression checking one property against a list of values while maintaining an optimal
 219    /// </summary>
 220    /// <typeparam name="TEntity">The entity.</typeparam>
 221    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 222    /// <param name="oneOf">The list of items to check.</param>
 223    /// <param name="property">Property expression.</param>
 224    /// <returns>A Query.</returns>
 225    public static Expression<Func<TEntity, bool>> OneOrManyExpressionBuilder<TEntity, TProperty>(this IList<TProperty> o
 226    {
 535227        var parameter = Expression.Parameter(typeof(TEntity), "item");
 535228        property = ParameterReplacer.Replace<Func<TEntity, TProperty>, Func<TEntity, TProperty>>(property, property.Para
 535229        if (oneOf.Count == 1)
 230        {
 441231            var value = oneOf[0];
 441232            if (typeof(TProperty).IsValueType)
 233            {
 253234                return Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(property.Body, Expression.Constant(value)
 235            }
 236            else
 237            {
 188238                return Expression.Lambda<Func<TEntity, bool>>(Expression.ReferenceEqual(property.Body, Expression.Consta
 239            }
 240        }
 241
 94242        var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericC
 243
 244        // Always wrap the collection in EF.Parameter so EF Core caches a single compiled plan and reuses it across call
 94245        return Expression.Lambda<Func<TEntity, bool>>(
 94246            Expression.Call(
 94247                null,
 94248                containsMethodInfo,
 94249                Expression.Call(null, _efParameterInstruction.MakeGenericMethod(oneOf.GetType()), Expression.Constant(on
 94250                property.Body),
 94251            parameter);
 252    }
 253
 254    internal static class ParameterReplacer
 255    {
 256        // Produces an expression identical to 'expression'
 257        // except with 'source' parameter replaced with 'target' expression.
 258        internal static Expression<TOutput> Replace<TInput, TOutput>(
 259                        Expression<TInput> expression,
 260                        ParameterExpression source,
 261                        ParameterExpression target)
 262        {
 535263            return new ParameterReplacerVisitor<TOutput>(source, target)
 535264                        .VisitAndConvert(expression);
 265        }
 266
 267        private sealed class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
 268        {
 269            private readonly ParameterExpression _source;
 270            private readonly ParameterExpression _target;
 271
 535272            public ParameterReplacerVisitor(ParameterExpression source, ParameterExpression target)
 273            {
 535274                _source = source;
 535275                _target = target;
 535276            }
 277
 278            internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
 279            {
 535280                return (Expression<TOutput>)VisitLambda(root);
 281            }
 282
 283            protected override Expression VisitLambda<T>(Expression<T> node)
 284            {
 285                // Leave all parameters alone except the one we want to replace.
 535286                var parameters = node.Parameters.Select(p => p == _source ? _target : p);
 287
 535288                return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
 289            }
 290
 291            protected override Expression VisitParameter(ParameterExpression node)
 292            {
 293                // Replace the source with the target, visit other params as usual.
 535294                return node == _source ? _target : base.VisitParameter(node);
 295            }
 296        }
 297    }
 298}

Methods/Properties

.cctor()
WhereOneOrMany(System.Linq.IQueryable`1<TEntity>,System.Collections.Generic.IList`1<TProperty>,System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
WhereReferencedItem(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,Jellyfin.Database.Implementations.Entities.ItemValueType,System.Collections.Generic.IList`1<System.Guid>,System.Boolean)
WhereReferencedItemMultipleTypes(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,System.Collections.Generic.IList`1<Jellyfin.Database.Implementations.Entities.ItemValueType>,System.Collections.Generic.IList`1<System.Guid>,System.Boolean)
ReferencedItemFilterExpressionBuilder(Jellyfin.Database.Implementations.JellyfinDbContext,Jellyfin.Database.Implementations.Entities.ItemValueType,System.Collections.Generic.IList`1<System.Guid>,System.Boolean)
WhereHasAnyProviderIds(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String[]>)
WhereHasAnyProviderId(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
WhereExcludeProviderIds(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
OneOrManyExpressionBuilder(System.Collections.Generic.IList`1<TProperty>,System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
Replace(System.Linq.Expressions.Expression`1<TInput>,System.Linq.Expressions.ParameterExpression,System.Linq.Expressions.ParameterExpression)
.ctor(System.Linq.Expressions.ParameterExpression,System.Linq.Expressions.ParameterExpression)
VisitAndConvert(System.Linq.Expressions.Expression`1<T>)
VisitLambda(System.Linq.Expressions.Expression`1<T>)
VisitParameter(System.Linq.Expressions.ParameterExpression)