< Summary

Information
Class: Pozitron.QuerySpecification.LikeEvaluator
Assembly: Pozitron.QuerySpecification.EntityFrameworkCore
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeEvaluator.cs
Tag: 52_11740816328
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 113
Line coverage: 100%
Branch coverage
100%
Covered branches: 26
Total branches: 26
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.cctor()100%11100%
Evaluate(...)100%88100%
ApplyLike(...)100%66100%
GetLikeCount(...)100%44100%
FillSorted(...)100%88100%

File(s)

/home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeEvaluator.cs

#LineLine coverage
 1using System.Buffers;
 2
 3namespace Pozitron.QuerySpecification;
 4
 5/*
 6    public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
 7    {
 8        foreach (var likeGroup in specification.LikeExpressions.GroupBy(x => x.Group))
 9        {
 10            source = source.Like(likeGroup);
 11        }
 12        return source;
 13    }
 14    This was the previous implementation. We're trying to avoid allocations of LikeExpressions and GroupBy.
 15    The new implementation preserves the behavior and has zero allocations.
 16    Instead of GroupBy, we have a single array, sorted by group, and we slice it to get the groups.
 17*/
 18
 19/// <summary>
 20/// Evaluates a specification to apply "like" expressions for filtering.
 21/// </summary>
 22public sealed class LikeEvaluator : IEvaluator
 23{
 224    private LikeEvaluator() { }
 25
 26    /// <summary>
 27    /// Gets the singleton instance of the <see cref="LikeEvaluator"/> class.
 28    /// </summary>
 129    public static LikeEvaluator Instance = new();
 30
 31    /// <inheritdoc/>
 32    public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
 33    {
 5334        var likeCount = GetLikeCount(specification);
 9535        if (likeCount == 0) return source;
 36
 1137        if (likeCount == 1)
 38        {
 39            // Specs with a single Like are the most common. We can optimize for this case to avoid all the additional o
 240            var items = specification.Items;
 641            for (var i = 0; i < items.Length; i++)
 42            {
 343                if (items[i].Type == ItemType.Like)
 44                {
 245                    return source.ApplyLikesAsOrGroup(items.Slice(i, 1));
 46                }
 47            }
 48        }
 49
 950        SpecItem[] array = ArrayPool<SpecItem>.Shared.Rent(likeCount);
 51
 52        try
 53        {
 954            var span = array.AsSpan()[..likeCount];
 955            FillSorted(specification, span);
 956            source = ApplyLike(source, span);
 957        }
 58        finally
 59        {
 960            ArrayPool<SpecItem>.Shared.Return(array);
 961        }
 62
 963        return source;
 64    }
 65
 66    private static IQueryable<T> ApplyLike<T>(IQueryable<T> source, ReadOnlySpan<SpecItem> span) where T : class
 67    {
 968        var groupStart = 0;
 7469        for (var i = 1; i <= span.Length; i++)
 70        {
 71            // If we reached the end of the span or the group has changed, we slice and process the group.
 2872            if (i == span.Length || span[i].Bag != span[groupStart].Bag)
 73            {
 1974                source = source.ApplyLikesAsOrGroup(span[groupStart..i]);
 1975                groupStart = i;
 76            }
 77        }
 978        return source;
 79    }
 80
 81    private static int GetLikeCount<T>(Specification<T> specification)
 82    {
 5383        var count = 0;
 56684        foreach (var item in specification.Items)
 85        {
 23086            if (item.Type == ItemType.Like)
 3087                count++;
 88        }
 5389        return count;
 90    }
 91
 92    private static void FillSorted<T>(Specification<T> specification, Span<SpecItem> span)
 93    {
 994        var i = 0;
 25495        foreach (var item in specification.Items)
 96        {
 11897            if (item.Type == ItemType.Like)
 98            {
 99                // Find the correct insertion point
 28100                var j = i;
 29101                while (j > 0 && span[j - 1].Bag > item.Bag)
 102                {
 1103                    span[j] = span[j - 1];
 1104                    j--;
 105                }
 106
 107                // Insert the current item in the sorted position
 28108                span[j] = item;
 28109                i++;
 110            }
 111        }
 9112    }
 113}