< 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
32%
Covered lines: 24
Uncovered lines: 49
Coverable lines: 73
Total lines: 281
Line coverage: 32.8%
Branch coverage
36%
Covered branches: 8
Total branches: 22
Branch coverage: 36.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/5/2026 - 12:13:57 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: 281 3/5/2026 - 12:13:57 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: 281

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{
 119    private static readonly MethodInfo _containsMethodGenericCache = typeof(Enumerable).GetMethods(BindingFlags.Public |
 120    private static readonly MethodInfo _efParameterInstruction = typeof(EF).GetMethod(nameof(EF.Parameter), BindingFlags
 121    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    {
 30034        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<ItemValue, ItemValueType>(itemValueTypes, iv => iv.Type);
 74
 075        return baseQuery.Where(item =>
 076            context.ItemValues
 077                .Where(typeFilter)
 078                .Join(context.ItemValuesMap, e => e.ItemValueId, e => e.ItemValueId, (itemVal, map) => new { itemVal, ma
 079                .Any(val =>
 080                    context.BaseItems.Where(itemFilter).Any(e => e.CleanName == val.itemVal.CleanValue)
 081                    && val.map.ItemId == item.Id) == EF.Constant(!invert));
 82    }
 83
 84    /// <summary>
 85    /// Builds a query expression that checks referenced ItemValues for a cross BaseItem lookup.
 86    /// </summary>
 87    /// <param name="context">The database context.</param>
 88    /// <param name="itemValueType">The type of item value to reference.</param>
 89    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 90    /// <param name="invert">If set an exclusion check is performed instead.</param>
 91    /// <returns>A Query.</returns>
 92    public static Expression<Func<BaseItemEntity, bool>> ReferencedItemFilterExpressionBuilder(
 93        this JellyfinDbContext context,
 94        ItemValueType itemValueType,
 95        IList<Guid> referenceIds,
 96        bool invert = false)
 97    {
 98        // Well genre/artist/album etc items do not actually set the ItemValue of thier specitic types so we cannot matc
 99        /*
 100        "(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@Ge
 101        */
 102
 0103        var itemFilter = OneOrManyExpressionBuilder<BaseItemEntity, Guid>(referenceIds, f => f.Id);
 104
 0105        return item =>
 0106          context.ItemValues
 0107              .Join(context.ItemValuesMap, e => e.ItemValueId, e => e.ItemValueId, (item, map) => new { item, map })
 0108              .Any(val =>
 0109                  val.item.Type == itemValueType
 0110                  && context.BaseItems.Where(itemFilter).Any(e => e.CleanName == val.item.CleanValue)
 0111                  && val.map.ItemId == item.Id) == EF.Constant(!invert);
 112    }
 113
 114    /// <summary>
 115    /// Filters items that match any of the specified (provider name, value) pairs.
 116    /// </summary>
 117    /// <param name="baseQuery">The source query.</param>
 118    /// <param name="providerIds">Dictionary mapping provider names to arrays of values to match.</param>
 119    /// <returns>A filtered query.</returns>
 120    public static IQueryable<BaseItemEntity> WhereHasAnyProviderIds(
 121        this IQueryable<BaseItemEntity> baseQuery,
 122        IReadOnlyDictionary<string, string[]> providerIds)
 123    {
 0124        var providerKeys = providerIds
 0125            .SelectMany(kvp => kvp.Value.Select(v => $"{kvp.Key}:{v}"))
 0126            .ToList();
 127
 0128        if (providerKeys.Count == 0)
 129        {
 0130            return baseQuery;
 131        }
 132
 0133        return baseQuery.Where(e => e.Provider!.Any(p => providerKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
 134    }
 135
 136    /// <summary>
 137    /// Filters items that have any of the specified providers. Empty/null values match any value for that provider.
 138    /// </summary>
 139    /// <param name="baseQuery">The source query.</param>
 140    /// <param name="providerIds">Dictionary mapping provider names to optional values.</param>
 141    /// <returns>A filtered query.</returns>
 142    public static IQueryable<BaseItemEntity> WhereHasAnyProviderId(
 143        this IQueryable<BaseItemEntity> baseQuery,
 144        IReadOnlyDictionary<string, string> providerIds)
 145    {
 0146        var existenceOnly = providerIds
 0147            .Where(e => string.IsNullOrEmpty(e.Value))
 0148            .Select(e => e.Key)
 0149            .ToList();
 150
 0151        var specificValues = providerIds
 0152            .Where(e => !string.IsNullOrEmpty(e.Value))
 0153            .Select(e => $"{e.Key}:{e.Value}")
 0154            .ToList();
 155
 0156        if (existenceOnly.Count == 0 && specificValues.Count == 0)
 157        {
 0158            return baseQuery;
 159        }
 160
 0161        if (existenceOnly.Count == 0)
 162        {
 0163            return baseQuery.Where(e => e.Provider!.Any(p =>
 0164                specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
 165        }
 166
 0167        if (specificValues.Count == 0)
 168        {
 0169            return baseQuery.Where(e => e.Provider!.Any(p => existenceOnly.Contains(p.ProviderId)));
 170        }
 171
 172        // Single EXISTS over Provider with both predicates OR'd, instead of two separate subqueries.
 0173        return baseQuery.Where(e => e.Provider!.Any(p =>
 0174            existenceOnly.Contains(p.ProviderId) ||
 0175            specificValues.Contains(p.ProviderId + ":" + p.ProviderValue)));
 176    }
 177
 178    /// <summary>
 179    /// Excludes items that match any of the specified (provider name, value) pairs.
 180    /// </summary>
 181    /// <param name="baseQuery">The source query.</param>
 182    /// <param name="providerIds">Dictionary mapping provider names to values to exclude.</param>
 183    /// <returns>A filtered query.</returns>
 184    public static IQueryable<BaseItemEntity> WhereExcludeProviderIds(
 185        this IQueryable<BaseItemEntity> baseQuery,
 186        IReadOnlyDictionary<string, string> providerIds)
 187    {
 0188        var excludeKeys = providerIds
 0189            .Select(e => $"{e.Key}:{e.Value}")
 0190            .ToList();
 191
 0192        if (excludeKeys.Count == 0)
 193        {
 0194            return baseQuery;
 195        }
 196
 0197        return baseQuery.Where(e => e.Provider!.All(p => !excludeKeys.Contains(p.ProviderId + ":" + p.ProviderValue)));
 198    }
 199
 200    /// <summary>
 201    /// Builds an optimised query expression checking one property against a list of values while maintaining an optimal
 202    /// </summary>
 203    /// <typeparam name="TEntity">The entity.</typeparam>
 204    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 205    /// <param name="oneOf">The list of items to check.</param>
 206    /// <param name="property">Property expression.</param>
 207    /// <returns>A Query.</returns>
 208    public static Expression<Func<TEntity, bool>> OneOrManyExpressionBuilder<TEntity, TProperty>(this IList<TProperty> o
 209    {
 343210        var parameter = Expression.Parameter(typeof(TEntity), "item");
 343211        property = ParameterReplacer.Replace<Func<TEntity, TProperty>, Func<TEntity, TProperty>>(property, property.Para
 343212        if (oneOf.Count == 1)
 213        {
 260214            var value = oneOf[0];
 260215            if (typeof(TProperty).IsValueType)
 216            {
 81217                return Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(property.Body, Expression.Constant(value)
 218            }
 219            else
 220            {
 179221                return Expression.Lambda<Func<TEntity, bool>>(Expression.ReferenceEqual(property.Body, Expression.Consta
 222            }
 223        }
 224
 83225        var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericC
 226
 227        // Threshold picked from microbenchmarks on SQLite: inline IN(const,...) beats a
 228        // parameterized array lookup by ~5-10% up to ~32 elements.
 83229        if (oneOf.Count <= 32)
 230        {
 83231            return Expression.Lambda<Func<TEntity, bool>>(Expression.Call(null, containsMethodInfo, Expression.Constant(
 232        }
 233
 0234        return Expression.Lambda<Func<TEntity, bool>>(Expression.Call(null, containsMethodInfo, Expression.Call(null, _e
 235    }
 236
 237    internal static class ParameterReplacer
 238    {
 239        // Produces an expression identical to 'expression'
 240        // except with 'source' parameter replaced with 'target' expression.
 241        internal static Expression<TOutput> Replace<TInput, TOutput>(
 242                        Expression<TInput> expression,
 243                        ParameterExpression source,
 244                        ParameterExpression target)
 245        {
 343246            return new ParameterReplacerVisitor<TOutput>(source, target)
 343247                        .VisitAndConvert(expression);
 248        }
 249
 250        private sealed class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
 251        {
 252            private readonly ParameterExpression _source;
 253            private readonly ParameterExpression _target;
 254
 343255            public ParameterReplacerVisitor(ParameterExpression source, ParameterExpression target)
 256            {
 343257                _source = source;
 343258                _target = target;
 343259            }
 260
 261            internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
 262            {
 343263                return (Expression<TOutput>)VisitLambda(root);
 264            }
 265
 266            protected override Expression VisitLambda<T>(Expression<T> node)
 267            {
 268                // Leave all parameters alone except the one we want to replace.
 343269                var parameters = node.Parameters.Select(p => p == _source ? _target : p);
 270
 343271                return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
 272            }
 273
 274            protected override Expression VisitParameter(ParameterExpression node)
 275            {
 276                // Replace the source with the target, visit other params as usual.
 343277                return node == _source ? _target : base.VisitParameter(node);
 278            }
 279        }
 280    }
 281}

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)