< 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
47%
Covered lines: 40
Uncovered lines: 44
Coverable lines: 84
Total lines: 311
Line coverage: 47.6%
Branch coverage
30%
Covered branches: 9
Total branches: 30
Branch coverage: 30%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/8/2026 - 12:15:13 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: 2988/11/2026 - 12:17:00 AM Line coverage: 47.6% (40/84) Branch coverage: 30% (9/30) Total lines: 311 5/8/2026 - 12:15:13 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: 2988/11/2026 - 12:17:00 AM Line coverage: 47.6% (40/84) Branch coverage: 30% (9/30) Total lines: 311

Coverage delta

Coverage delta 54 -54

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
WhereOneOrMany(...)100%11100%
OneOrManyExpressionBuilder(...)100%88100%
EfParameterFor(...)100%11100%
WhereReferencedItem(...)100%210%
WhereReferencedItem(...)0%620%
WhereHasAnyProviderIds(...)100%210%
WhereHasAnyProviderId(...)100%210%
WhereExcludeProviderIds(...)100%210%
Flatten()0%7280%
WhereProviderMatch(...)0%110100%
ProviderPredicate(...)100%210%
Replace(...)100%11100%
.ctor(...)100%11100%
VisitAndConvert(...)100%11100%
VisitLambda(...)100%11100%
VisitParameter(...)50%22100%

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>
 17/// <remarks>
 18/// Every helper here binds its values through <see cref="EF.Parameter{T}(T)"/>. Values embedded as bare
 19/// constants are inlined into the SQL as literals, which gives each distinct value its own entry in EF's
 20/// compiled query cache and its own statement for the database to plan.
 21/// </remarks>
 22public static class JellyfinQueryHelperExtensions
 23{
 224    private static readonly MethodInfo _containsMethodGenericCache = typeof(Enumerable).GetMethods(BindingFlags.Public |
 225    private static readonly MethodInfo _efParameterInstruction = typeof(EF).GetMethod(nameof(EF.Parameter), BindingFlags
 226    private static readonly ConcurrentDictionary<Type, MethodInfo> _containsQueryCache = new();
 227    private static readonly ConcurrentDictionary<Type, MethodInfo> _efParameterCache = new();
 28
 29    /// <summary>
 30    /// Builds an optimised query checking one property against a list of values while maintaining an optimal query.
 31    /// </summary>
 32    /// <typeparam name="TEntity">The entity.</typeparam>
 33    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 34    /// <param name="query">The source query.</param>
 35    /// <param name="oneOf">The list of items to check. An empty list matches nothing.</param>
 36    /// <param name="property">Property expression.</param>
 37    /// <returns>A Query.</returns>
 38    public static IQueryable<TEntity> WhereOneOrMany<TEntity, TProperty>(this IQueryable<TEntity> query, IReadOnlyList<T
 39    {
 49740        return query.Where(OneOrManyExpressionBuilder(oneOf, property));
 41    }
 42
 43    /// <summary>
 44    /// Builds an optimised query expression checking one property against a list of values while maintaining an optimal
 45    /// </summary>
 46    /// <typeparam name="TEntity">The entity.</typeparam>
 47    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 48    /// <param name="oneOf">The list of items to check. An empty list matches nothing.</param>
 49    /// <param name="property">Property expression.</param>
 50    /// <returns>A Query.</returns>
 51    public static Expression<Func<TEntity, bool>> OneOrManyExpressionBuilder<TEntity, TProperty>(this IReadOnlyList<TPro
 52    {
 54353        ArgumentNullException.ThrowIfNull(oneOf);
 54354        ArgumentNullException.ThrowIfNull(property);
 55
 54356        var parameter = Expression.Parameter(typeof(TEntity), "item");
 54357        property = ParameterReplacer.Replace<Func<TEntity, TProperty>, Func<TEntity, TProperty>>(property, property.Para
 58
 54359        if (oneOf.Count == 0)
 60        {
 61            // Fail closed, and without asking the database to unpack an empty collection to prove it.
 162            return Expression.Lambda<Func<TEntity, bool>>(Expression.Constant(false), parameter);
 63        }
 64
 54265        if (oneOf.Count == 1)
 66        {
 44767            var value = Expression.Call(
 44768                null,
 44769                EfParameterFor(typeof(TProperty)),
 44770                Expression.Constant(oneOf[0], typeof(TProperty)));
 71
 44772            return Expression.Lambda<Func<TEntity, bool>>(
 44773                typeof(TProperty).IsValueType
 44774                    ? Expression.Equal(property.Body, value)
 44775                    : Expression.ReferenceEqual(property.Body, value),
 44776                parameter);
 77        }
 78
 9579        var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericC
 80
 81        // Binding the whole collection as one parameter keeps the statement identical for any element
 82        // count, instead of emitting one placeholder per element.
 9583        return Expression.Lambda<Func<TEntity, bool>>(
 9584            Expression.Call(
 9585                null,
 9586                containsMethodInfo,
 9587                Expression.Call(null, EfParameterFor(oneOf.GetType()), Expression.Constant(oneOf)),
 9588                property.Body),
 9589            parameter);
 90    }
 91
 92    private static MethodInfo EfParameterFor(Type type)
 93    {
 54294        return _efParameterCache.GetOrAdd(type, static (key) => _efParameterInstruction.MakeGenericMethod(key));
 95    }
 96
 97    /// <summary>
 98    /// Builds a query that checks referenced ItemValues for a cross BaseItem lookup.
 99    /// </summary>
 100    /// <param name="baseQuery">The source query.</param>
 101    /// <param name="context">The database context.</param>
 102    /// <param name="itemValueType">The type of item value to reference.</param>
 103    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 104    /// <param name="invert">If set an exclusion check is performed instead.</param>
 105    /// <returns>A Query.</returns>
 106    public static IQueryable<BaseItemEntity> WhereReferencedItem(
 107        this IQueryable<BaseItemEntity> baseQuery,
 108        JellyfinDbContext context,
 109        ItemValueType itemValueType,
 110        IReadOnlyList<Guid> referenceIds,
 111        bool invert = false)
 112    {
 0113        return baseQuery.WhereReferencedItem(context, [itemValueType], referenceIds, invert);
 114    }
 115
 116    /// <summary>
 117    /// Builds a query that checks referenced ItemValues of any of the given types for a cross BaseItem lookup.
 118    /// </summary>
 119    /// <param name="baseQuery">The source query.</param>
 120    /// <param name="context">The database context.</param>
 121    /// <param name="itemValueTypes">The types of item value to reference.</param>
 122    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 123    /// <param name="invert">If set an exclusion check is performed instead.</param>
 124    /// <returns>A Query.</returns>
 125    /// <remarks>
 126    /// Matching is on CleanName alone. Genre/artist/album etc items do not set an ItemValue of their own
 127    /// type, so the referenced item's Type is never consulted and ids whose names clean to the same value
 128    /// are interchangeable across types.
 129    /// </remarks>
 130    public static IQueryable<BaseItemEntity> WhereReferencedItem(
 131        this IQueryable<BaseItemEntity> baseQuery,
 132        JellyfinDbContext context,
 133        IReadOnlyList<ItemValueType> itemValueTypes,
 134        IReadOnlyList<Guid> referenceIds,
 135        bool invert = false)
 136    {
 0137        ArgumentNullException.ThrowIfNull(context);
 138
 139        // Flat sub-selects rather than a correlated .Any(...Any(...)).
 0140        var referencedCleanValues = context.BaseItems
 0141            .Where(OneOrManyExpressionBuilder<BaseItemEntity, Guid>(referenceIds, e => e.Id))
 0142            .Select(e => e.CleanName);
 143
 0144        var matchingItemIds = context.ItemValuesMap
 0145            .Where(OneOrManyExpressionBuilder<ItemValueMap, ItemValueType>(itemValueTypes, m => m.ItemValue.Type))
 0146            .Where(m => referencedCleanValues.Contains(m.ItemValue.CleanValue))
 0147            .Select(m => m.ItemId);
 148
 0149        return invert
 0150            ? baseQuery.Where(e => !matchingItemIds.Contains(e.Id))
 0151            : baseQuery.Where(e => matchingItemIds.Contains(e.Id));
 152    }
 153
 154    /// <summary>
 155    /// Filters items that have any of the specified providers, optionally restricted to given values.
 156    /// </summary>
 157    /// <param name="baseQuery">The source query.</param>
 158    /// <param name="providerIds">Dictionary mapping provider names to values to match. An empty value array matches any
 159    /// <returns>A filtered query.</returns>
 160    public static IQueryable<BaseItemEntity> WhereHasAnyProviderIds(
 161        this IQueryable<BaseItemEntity> baseQuery,
 162        IReadOnlyDictionary<string, string[]> providerIds)
 163    {
 0164        return baseQuery.WhereProviderMatch(Flatten(providerIds), false);
 165    }
 166
 167    /// <summary>
 168    /// Filters items that have any of the specified providers, optionally restricted to a given value.
 169    /// </summary>
 170    /// <param name="baseQuery">The source query.</param>
 171    /// <param name="providerIds">Dictionary mapping provider names to optional values. An empty value matches any value
 172    /// <returns>A filtered query.</returns>
 173    public static IQueryable<BaseItemEntity> WhereHasAnyProviderId(
 174        this IQueryable<BaseItemEntity> baseQuery,
 175        IReadOnlyDictionary<string, string> providerIds)
 176    {
 0177        return baseQuery.WhereProviderMatch(providerIds, false);
 178    }
 179
 180    /// <summary>
 181    /// Excludes items that have any of the specified providers, optionally restricted to a given value.
 182    /// </summary>
 183    /// <param name="baseQuery">The source query.</param>
 184    /// <param name="providerIds">Dictionary mapping provider names to optional values. An empty value excludes any valu
 185    /// <returns>A filtered query.</returns>
 186    public static IQueryable<BaseItemEntity> WhereExcludeProviderIds(
 187        this IQueryable<BaseItemEntity> baseQuery,
 188        IReadOnlyDictionary<string, string> providerIds)
 189    {
 0190        return baseQuery.WhereProviderMatch(providerIds, true);
 191    }
 192
 193    private static IEnumerable<KeyValuePair<string, string>> Flatten(IReadOnlyDictionary<string, string[]> providerIds)
 194    {
 0195        ArgumentNullException.ThrowIfNull(providerIds);
 196
 0197        foreach (var (provider, values) in providerIds)
 198        {
 0199            if (values is null || values.Length == 0)
 200            {
 0201                yield return new KeyValuePair<string, string>(provider, string.Empty);
 0202                continue;
 203            }
 204
 0205            foreach (var value in values)
 206            {
 0207                yield return new KeyValuePair<string, string>(provider, value);
 208            }
 0209        }
 0210    }
 211
 212    /// <summary>
 213    /// Matches items against a set of (provider, value) pairs, where an empty value means any value for
 214    /// that provider. Emits a single EXISTS over the provider collection with the predicates OR'd, rather
 215    /// than one subquery per predicate group.
 216    /// </summary>
 217    private static IQueryable<BaseItemEntity> WhereProviderMatch(
 218        this IQueryable<BaseItemEntity> baseQuery,
 219        IEnumerable<KeyValuePair<string, string>> providerIds,
 220        bool invert)
 221    {
 0222        ArgumentNullException.ThrowIfNull(providerIds);
 223
 0224        var existenceOnly = new List<string>();
 0225        var specificValues = new List<string>();
 0226        foreach (var (provider, value) in providerIds)
 227        {
 0228            if (string.IsNullOrEmpty(value))
 229            {
 0230                existenceOnly.Add(provider);
 231            }
 232            else
 233            {
 0234                specificValues.Add(provider + ":" + value);
 235            }
 236        }
 237
 0238        if (existenceOnly.Count == 0 && specificValues.Count == 0)
 239        {
 0240            return baseQuery;
 241        }
 242
 0243        var predicate = ProviderPredicate(existenceOnly, specificValues);
 244
 245        // NOT EXISTS rather than NOT IN: the latter yields no rows at all if the subquery can produce NULL.
 0246        return invert
 0247            ? baseQuery.Where(e => !e.Provider!.AsQueryable().Any(predicate))
 0248            : baseQuery.Where(e => e.Provider!.AsQueryable().Any(predicate));
 249    }
 250
 251    private static Expression<Func<BaseItemProvider, bool>> ProviderPredicate(
 252        IReadOnlyList<string> existenceOnly,
 253        IReadOnlyList<string> specificValues)
 254    {
 0255        var byProvider = existenceOnly.OneOrManyExpressionBuilder<BaseItemProvider, string>(p => p.ProviderId);
 0256        var byPair = specificValues.OneOrManyExpressionBuilder<BaseItemProvider, string>(p => p.ProviderId + ":" + p.Pro
 257
 258        // Both builders mint their own parameter; rebind so the two bodies can share one lambda.
 0259        var parameter = byProvider.Parameters[0];
 0260        var reboundPair = ParameterReplacer.Replace<Func<BaseItemProvider, bool>, Func<BaseItemProvider, bool>>(byPair, 
 261
 0262        return Expression.Lambda<Func<BaseItemProvider, bool>>(
 0263            Expression.OrElse(byProvider.Body, reboundPair.Body),
 0264            parameter);
 265    }
 266
 267    internal static class ParameterReplacer
 268    {
 269        // Produces an expression identical to 'expression'
 270        // except with 'source' parameter replaced with 'target' expression.
 271        internal static Expression<TOutput> Replace<TInput, TOutput>(
 272                        Expression<TInput> expression,
 273                        ParameterExpression source,
 274                        ParameterExpression target)
 275        {
 543276            return new ParameterReplacerVisitor<TOutput>(source, target)
 543277                        .VisitAndConvert(expression);
 278        }
 279
 280        private sealed class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
 281        {
 282            private readonly ParameterExpression _source;
 283            private readonly ParameterExpression _target;
 284
 543285            public ParameterReplacerVisitor(ParameterExpression source, ParameterExpression target)
 286            {
 543287                _source = source;
 543288                _target = target;
 543289            }
 290
 291            internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
 292            {
 543293                return (Expression<TOutput>)VisitLambda(root);
 294            }
 295
 296            protected override Expression VisitLambda<T>(Expression<T> node)
 297            {
 298                // Leave all parameters alone except the one we want to replace.
 543299                var parameters = node.Parameters.Select(p => p == _source ? _target : p);
 300
 543301                return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
 302            }
 303
 304            protected override Expression VisitParameter(ParameterExpression node)
 305            {
 306                // Replace the source with the target, visit other params as usual.
 543307                return node == _source ? _target : base.VisitParameter(node);
 308            }
 309        }
 310    }
 311}

Methods/Properties

.cctor()
WhereOneOrMany(System.Linq.IQueryable`1<TEntity>,System.Collections.Generic.IReadOnlyList`1<TProperty>,System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
OneOrManyExpressionBuilder(System.Collections.Generic.IReadOnlyList`1<TProperty>,System.Linq.Expressions.Expression`1<System.Func`2<TEntity,TProperty>>)
EfParameterFor(System.Type)
WhereReferencedItem(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,Jellyfin.Database.Implementations.Entities.ItemValueType,System.Collections.Generic.IReadOnlyList`1<System.Guid>,System.Boolean)
WhereReferencedItem(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,Jellyfin.Database.Implementations.JellyfinDbContext,System.Collections.Generic.IReadOnlyList`1<Jellyfin.Database.Implementations.Entities.ItemValueType>,System.Collections.Generic.IReadOnlyList`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>)
Flatten()
WhereProviderMatch(System.Linq.IQueryable`1<Jellyfin.Database.Implementations.Entities.BaseItemEntity>,System.Collections.Generic.IEnumerable`1<System.Collections.Generic.KeyValuePair`2<System.String,System.String>>,System.Boolean)
ProviderPredicate(System.Collections.Generic.IReadOnlyList`1<System.String>,System.Collections.Generic.IReadOnlyList`1<System.String>)
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)