< Summary

Information
Class: Pozitron.QuerySpecification.LikeEvaluator
Assembly: Pozitron.QuerySpecification.EntityFrameworkCore
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification.EntityFrameworkCore/Evaluators/LikeEvaluator.cs
Tag: 67_15587897385
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 114
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 Cyclomatic complexity NPath complexity Sequence 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>
 22[EvaluatorDiscovery(Order = 20)]
 23public sealed class LikeEvaluator : IEvaluator
 24{
 425    private LikeEvaluator() { }
 26
 27    /// <summary>
 28    /// Gets the singleton instance of the <see cref="LikeEvaluator"/> class.
 29    /// </summary>
 230    public static LikeEvaluator Instance = new();
 31
 32    /// <inheritdoc/>
 33    public IQueryable<T> Evaluate<T>(IQueryable<T> source, Specification<T> specification) where T : class
 34    {
 5335        var likeCount = GetLikeCount(specification);
 9536        if (likeCount == 0) return source;
 37
 1138        if (likeCount == 1)
 39        {
 40            // Specs with a single Like are the most common. We can optimize for this case to avoid all the additional o
 241            var items = specification.Items;
 642            for (var i = 0; i < items.Length; i++)
 43            {
 344                if (items[i].Type == ItemType.Like)
 45                {
 246                    return source.ApplyLikesAsOrGroup(items.Slice(i, 1));
 47                }
 48            }
 49        }
 50
 951        SpecItem[] array = ArrayPool<SpecItem>.Shared.Rent(likeCount);
 52
 53        try
 54        {
 955            var span = array.AsSpan()[..likeCount];
 956            FillSorted(specification, span);
 957            source = ApplyLike(source, span);
 958        }
 59        finally
 60        {
 961            ArrayPool<SpecItem>.Shared.Return(array);
 962        }
 63
 964        return source;
 65    }
 66
 67    private static IQueryable<T> ApplyLike<T>(IQueryable<T> source, ReadOnlySpan<SpecItem> span) where T : class
 68    {
 969        var groupStart = 0;
 7470        for (var i = 1; i <= span.Length; i++)
 71        {
 72            // If we reached the end of the span or the group has changed, we slice and process the group.
 2873            if (i == span.Length || span[i].Bag != span[groupStart].Bag)
 74            {
 1975                source = source.ApplyLikesAsOrGroup(span[groupStart..i]);
 1976                groupStart = i;
 77            }
 78        }
 979        return source;
 80    }
 81
 82    private static int GetLikeCount<T>(Specification<T> specification)
 83    {
 5384        var count = 0;
 59085        foreach (var item in specification.Items)
 86        {
 24287            if (item.Type == ItemType.Like)
 3088                count++;
 89        }
 5390        return count;
 91    }
 92
 93    private static void FillSorted<T>(Specification<T> specification, Span<SpecItem> span)
 94    {
 995        var i = 0;
 27896        foreach (var item in specification.Items)
 97        {
 13098            if (item.Type == ItemType.Like)
 99            {
 100                // Find the correct insertion point
 28101                var j = i;
 29102                while (j > 0 && span[j - 1].Bag > item.Bag)
 103                {
 1104                    span[j] = span[j - 1];
 1105                    j--;
 106                }
 107
 108                // Insert the current item in the sorted position
 28109                span[j] = item;
 28110                i++;
 111            }
 112        }
 9113    }
 114}