< Summary

Information
Class: Pozitron.QuerySpecification.LikeMemoryEvaluator
Assembly: Pozitron.QuerySpecification
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs
Tag: 52_11740816328
Line coverage
100%
Covered lines: 44
Uncovered lines: 0
Coverable lines: 44
Total lines: 140
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
Evaluate(...)100%66100%
.ctor(...)100%11100%
Clone()100%11100%
Dispose()100%22100%
MoveNext()100%88100%
IsValid(...)100%88100%
IsValidInOrGroup()100%88100%

File(s)

/home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs

#LineLine coverage
 1using System.Diagnostics;
 2
 3namespace Pozitron.QuerySpecification;
 4
 5/*
 6    public IEnumerable<T> Evaluate<T>(IEnumerable<T> source, Specification<T> specification)
 7    {
 8        foreach (var likeGroup in specification.LikeExpressions.GroupBy(x => x.Group))
 9        {
 10            source = source.Where(x => likeGroup.Any(c => c.KeySelectorFunc(x)?.Like(c.Pattern) ?? false));
 11        }
 12        return source;
 13    }
 14    This was the previous implementation. We're trying to avoid allocations of LikeExpressions, GroupBy and LINQ.
 15    The new implementation preserves the behavior and reduces allocations drastically.
 16    We've implemented a custom iterator. Also, instead of GroupBy, we have a single array sorted by group, and we slice 
 17    For source of 1000 items, the allocations are reduced from 257.872 bytes to only 64 bytes (the cost of the iterator 
 18 */
 19
 20/// <summary>
 21/// Represents an in-memory evaluator for "Like" expressions.
 22/// </summary>
 23public sealed class LikeMemoryEvaluator : IInMemoryEvaluator
 24{
 25    /// <summary>
 26    /// Gets the singleton instance of the <see cref="LikeMemoryEvaluator"/> class.
 27    /// </summary>
 128    public static LikeMemoryEvaluator Instance = new();
 229    private LikeMemoryEvaluator() { }
 30
 31    /// <inheritdoc/>
 32    public IEnumerable<T> Evaluate<T>(IEnumerable<T> source, Specification<T> specification)
 33    {
 1734        var compiledItems = specification.GetCompiledItems();
 1835        if (compiledItems.Length == 0) return source;
 36
 4437        var startIndexLikeItems = Array.FindIndex(compiledItems, item => item.Type == ItemType.Like);
 2338        if (startIndexLikeItems == -1) return source;
 39
 40        // The like items are contiguously placed as a last segment in the array and are already sorted by group.
 941        return new SpecLikeIterator<T>(source, compiledItems, startIndexLikeItems);
 42    }
 43
 44    private sealed class SpecLikeIterator<TSource> : Iterator<TSource>
 45    {
 46        private readonly IEnumerable<TSource> _source;
 47        private readonly SpecItem[] _compiledItems;
 48        private readonly int _startIndex;
 49
 50        private IEnumerator<TSource>? _enumerator;
 51
 1052        public SpecLikeIterator(IEnumerable<TSource> source, SpecItem[] compiledItems, int startIndex)
 53        {
 54            Debug.Assert(source != null);
 55            Debug.Assert(compiledItems != null);
 1056            _source = source;
 1057            _compiledItems = compiledItems;
 1058            _startIndex = startIndex;
 1059        }
 60
 61        public override Iterator<TSource> Clone()
 162            => new SpecLikeIterator<TSource>(_source, _compiledItems, _startIndex);
 63
 64        public override void Dispose()
 65        {
 1466            if (_enumerator is not null)
 67            {
 1068                _enumerator.Dispose();
 1069                _enumerator = null;
 70            }
 1471            base.Dispose();
 1472        }
 73
 74        public override bool MoveNext()
 75        {
 2576            switch (_state)
 77            {
 78                case 1:
 1079                    _enumerator = _source.GetEnumerator();
 1080                    _state = 2;
 81                    goto case 2;
 82                case 2:
 83                    Debug.Assert(_enumerator is not null);
 2584                    var likeItems = _compiledItems.AsSpan()[_startIndex.._compiledItems.Length];
 3585                    while (_enumerator.MoveNext())
 86                    {
 3187                        TSource sourceItem = _enumerator.Current;
 3188                        if (IsValid(sourceItem, likeItems))
 89                        {
 2190                            _current = sourceItem;
 2191                            return true;
 92                        }
 93                    }
 94
 495                    Dispose();
 96                    break;
 97            }
 98
 499            return false;
 100        }
 101
 102        private static bool IsValid<T>(T sourceItem, ReadOnlySpan<SpecItem> span)
 103        {
 31104            var valid = true;
 31105            var groupStart = 0;
 106
 134107            for (var i = 1; i <= span.Length; i++)
 108            {
 109                // If we reached the end of the span or the group has changed, we slice and process the group.
 46110                if (i == span.Length || span[i].Bag != span[groupStart].Bag)
 111                {
 35112                    var validOrGroup = IsValidInOrGroup(sourceItem, span[groupStart..i]);
 35113                    if ((valid = valid && validOrGroup) is false)
 114                    {
 115                        break;
 116                    }
 25117                    groupStart = i;
 118                }
 119            }
 120
 31121            return valid;
 122
 123            static bool IsValidInOrGroup(T sourceItem, ReadOnlySpan<SpecItem> span)
 124            {
 35125                var validOrGroup = false;
 131126                foreach (var specItem in span)
 127                {
 43128                    if (specItem.Reference is not SpecLikeCompiled<T> specLike) continue;
 129
 43130                    if (specLike.KeySelector(sourceItem)?.Like(specLike.Pattern) ?? false)
 131                    {
 25132                        validOrGroup = true;
 25133                        break;
 134                    }
 135                }
 35136                return validOrGroup;
 137            }
 138        }
 139    }
 140}