< 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
73%
Covered lines: 25
Uncovered lines: 9
Coverable lines: 34
Total lines: 166
Line coverage: 73.5%
Branch coverage
90%
Covered branches: 9
Total branches: 10
Branch coverage: 90%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
WhereOneOrMany(...)100%11100%
WhereReferencedItem(...)100%210%
ReferencedItemFilterExpressionBuilder(...)100%210%
OneOrManyExpressionBuilder(...)100%88100%
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>
 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    {
 11834        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 expression that checks referenced ItemValues for a cross BaseItem lookup.
 58    /// </summary>
 59    /// <param name="context">The database context.</param>
 60    /// <param name="itemValueType">The type of item value to reference.</param>
 61    /// <param name="referenceIds">The list of BaseItem ids to check matches.</param>
 62    /// <param name="invert">If set an exclusion check is performed instead.</param>
 63    /// <returns>A Query.</returns>
 64    public static Expression<Func<BaseItemEntity, bool>> ReferencedItemFilterExpressionBuilder(
 65        this JellyfinDbContext context,
 66        ItemValueType itemValueType,
 67        IList<Guid> referenceIds,
 68        bool invert = false)
 69    {
 70        // Well genre/artist/album etc items do not actually set the ItemValue of thier specitic types so we cannot matc
 71        /*
 72        "(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=@Ge
 73        */
 74
 075        var itemFilter = OneOrManyExpressionBuilder<BaseItemEntity, Guid>(referenceIds, f => f.Id);
 76
 077        return item =>
 078          context.ItemValues
 079              .Join(context.ItemValuesMap, e => e.ItemValueId, e => e.ItemValueId, (item, map) => new { item, map })
 080              .Any(val =>
 081                  val.item.Type == itemValueType
 082                  && context.BaseItems.Where(itemFilter).Any(e => e.CleanName == val.item.CleanValue)
 083                  && val.map.ItemId == item.Id) == EF.Constant(!invert);
 84    }
 85
 86    /// <summary>
 87    /// Builds an optimised query expression checking one property against a list of values while maintaining an optimal
 88    /// </summary>
 89    /// <typeparam name="TEntity">The entity.</typeparam>
 90    /// <typeparam name="TProperty">The property type to compare.</typeparam>
 91    /// <param name="oneOf">The list of items to check.</param>
 92    /// <param name="property">Property expression.</param>
 93    /// <returns>A Query.</returns>
 94    public static Expression<Func<TEntity, bool>> OneOrManyExpressionBuilder<TEntity, TProperty>(this IList<TProperty> o
 95    {
 11896        var parameter = Expression.Parameter(typeof(TEntity), "item");
 11897        property = ParameterReplacer.Replace<Func<TEntity, TProperty>, Func<TEntity, TProperty>>(property, property.Para
 11898        if (oneOf.Count == 1)
 99        {
 61100            var value = oneOf[0];
 61101            if (typeof(TProperty).IsValueType)
 102            {
 14103                return Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(property.Body, Expression.Constant(value)
 104            }
 105            else
 106            {
 47107                return Expression.Lambda<Func<TEntity, bool>>(Expression.ReferenceEqual(property.Body, Expression.Consta
 108            }
 109        }
 110
 57111        var containsMethodInfo = _containsQueryCache.GetOrAdd(typeof(TProperty), static (key) => _containsMethodGenericC
 112
 57113        if (oneOf.Count < 4) // arbitrary value choosen.
 114        {
 115            // if we have 3 or fewer values to check against its faster to do a IN(const,const,const) lookup
 44116            return Expression.Lambda<Func<TEntity, bool>>(Expression.Call(null, containsMethodInfo, Expression.Constant(
 117        }
 118
 13119        return Expression.Lambda<Func<TEntity, bool>>(Expression.Call(null, containsMethodInfo, Expression.Call(null, _e
 120    }
 121
 122    internal static class ParameterReplacer
 123    {
 124        // Produces an expression identical to 'expression'
 125        // except with 'source' parameter replaced with 'target' expression.
 126        internal static Expression<TOutput> Replace<TInput, TOutput>(
 127                        Expression<TInput> expression,
 128                        ParameterExpression source,
 129                        ParameterExpression target)
 130        {
 118131            return new ParameterReplacerVisitor<TOutput>(source, target)
 118132                        .VisitAndConvert(expression);
 133        }
 134
 135        private sealed class ParameterReplacerVisitor<TOutput> : ExpressionVisitor
 136        {
 137            private readonly ParameterExpression _source;
 138            private readonly ParameterExpression _target;
 139
 118140            public ParameterReplacerVisitor(ParameterExpression source, ParameterExpression target)
 141            {
 118142                _source = source;
 118143                _target = target;
 118144            }
 145
 146            internal Expression<TOutput> VisitAndConvert<T>(Expression<T> root)
 147            {
 118148                return (Expression<TOutput>)VisitLambda(root);
 149            }
 150
 151            protected override Expression VisitLambda<T>(Expression<T> node)
 152            {
 153                // Leave all parameters alone except the one we want to replace.
 118154                var parameters = node.Parameters.Select(p => p == _source ? _target : p);
 155
 118156                return Expression.Lambda<TOutput>(Visit(node.Body), parameters);
 157            }
 158
 159            protected override Expression VisitParameter(ParameterExpression node)
 160            {
 161                // Replace the source with the target, visit other params as usual.
 118162                return node == _source ? _target : base.VisitParameter(node);
 163            }
 164        }
 165    }
 166}