< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.OrderMapper
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/OrderMapper.cs
Line coverage
31%
Covered lines: 20
Uncovered lines: 44
Coverable lines: 64
Total lines: 123
Line coverage: 31.2%
Branch coverage
21%
Covered branches: 8
Total branches: 37
Branch coverage: 21.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/2/2026 - 12:12:50 AM Line coverage: 23.5% (12/51) Branch coverage: 15.1% (5/33) Total lines: 985/4/2026 - 12:15:16 AM Line coverage: 18.5% (10/54) Branch coverage: 15.1% (5/33) Total lines: 1057/6/2026 - 12:16:28 AM Line coverage: 21.3% (13/61) Branch coverage: 17.1% (6/35) Total lines: 1127/27/2026 - 12:16:14 AM Line coverage: 31.2% (20/64) Branch coverage: 21.6% (8/37) Total lines: 123 5/2/2026 - 12:12:50 AM Line coverage: 23.5% (12/51) Branch coverage: 15.1% (5/33) Total lines: 985/4/2026 - 12:15:16 AM Line coverage: 18.5% (10/54) Branch coverage: 15.1% (5/33) Total lines: 1057/6/2026 - 12:16:28 AM Line coverage: 21.3% (13/61) Branch coverage: 17.1% (6/35) Total lines: 1127/27/2026 - 12:16:14 AM Line coverage: 31.2% (20/64) Branch coverage: 21.6% (8/37) Total lines: 123

Coverage delta

Coverage delta 10 -10

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MapOrderByField(...)22.85%3273538%
MapSearchRelevanceOrder(...)100%210%
GetCleanValue(...)0%620%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/OrderMapper.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2#pragma warning disable CA1304 // Specify CultureInfo
 3#pragma warning disable CA1311 // Specify a culture or use an invariant version
 4#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string compari
 5
 6using System;
 7using System.Linq;
 8using System.Linq.Expressions;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Database.Implementations;
 11using Jellyfin.Database.Implementations.Entities;
 12using Jellyfin.Extensions;
 13using MediaBrowser.Controller.Entities;
 14using Microsoft.EntityFrameworkCore;
 15
 16namespace Jellyfin.Server.Implementations.Item;
 17
 18/// <summary>
 19/// Static class for methods which maps types of ordering to their respecting ordering functions.
 20/// </summary>
 21public static class OrderMapper
 22{
 23    /// <summary>
 24    /// Creates Func to be executed later with a given BaseItemEntity input for sorting items on query.
 25    /// </summary>
 26    /// <param name="sortBy">Item property to sort by.</param>
 27    /// <param name="query">Context Query.</param>
 28    /// <param name="jellyfinDbContext">Context.</param>
 29    /// <returns>Func to be executed later for sorting query.</returns>
 30    public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query,
 31    {
 16732        if (sortBy == ItemSortBy.DatePlayed)
 33        {
 34            // An item's played date is the newest of its own progress and that of its alternate versions,
 35            // which track progress under their own ids. Matching both in one predicate ORs them together,
 36            // which no index can serve: the user's whole UserData table gets scanned per sorted row.
 37            // Two indexed lookups combined by MAX cost a seek each instead.
 138            var userData = query.User is null
 139                ? jellyfinDbContext.UserData
 140                : jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id);
 41
 142            return e => userData
 143                .Where(w => w.ItemId == e.Id)
 144                .Select(w => w.LastPlayedDate)
 145                .Concat(userData
 146                    .Where(w => w.Item!.PrimaryVersionId == e.Id)
 147                    .Select(w => w.LastPlayedDate))
 148                .Max();
 49        }
 50
 16651        return (sortBy, query.User) switch
 16652        {
 053            (ItemSortBy.AirTime, _) => e => e.SortName,
 054            (ItemSortBy.Runtime, _) => e => e.RunTimeTicks,
 7155            (ItemSortBy.Random, _) => e => EF.Functions.Random(),
 056            (ItemSortBy.PlayCount, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.Cus
 057            (ItemSortBy.IsFavoriteOrLiked, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f 
 4458            (ItemSortBy.IsFolder, _) => e => e.IsFolder,
 059            (ItemSortBy.IsPlayed, _) => e => e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.Cust
 060            (ItemSortBy.IsUnplayed, _) => e => !e.UserData!.Where(f => f.UserId.Equals(query.User!.Id)).OrderBy(f => f.C
 061            (ItemSortBy.DateLastContentAdded, _) => e => e.DateLastMediaAdded,
 062            (ItemSortBy.Artist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).OrderBy(f 
 063            (ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).
 064            (ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).OrderBy(f
 065            (ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue,
 066            (ItemSortBy.SeriesSortName, _) => e => e.SeriesName,
 067            (ItemSortBy.Album, _) => e => e.Album,
 068            (ItemSortBy.DateCreated, _) => e => e.DateCreated,
 169            (ItemSortBy.PremiereDate, _) => e => e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYea
 070            (ItemSortBy.StartDate, _) => e => e.StartDate,
 071            (ItemSortBy.Name, _) => e => e.CleanName,
 072            (ItemSortBy.CommunityRating, _) => e => e.CommunityRating,
 073            (ItemSortBy.ProductionYear, _) => e => e.ProductionYear,
 074            (ItemSortBy.CriticRating, _) => e => e.CriticRating,
 075            (ItemSortBy.VideoBitRate, _) => e => e.TotalBitrate,
 076            (ItemSortBy.ParentIndexNumber, _) => e => e.ParentIndexNumber,
 077            (ItemSortBy.IndexNumber, _) => e => e.IndexNumber,
 16678            // SeriesDatePlayed is normally handled via pre-aggregated join in ApplySeriesDatePlayedOrder.
 16679            // This correlated subquery fallback is only reached when combined with search.
 080            (ItemSortBy.SeriesDatePlayed, not null) => e =>
 081                jellyfinDbContext.UserData
 082                    .Where(w => w.UserId == query.User.Id && w.Played && w.Item!.SeriesPresentationUniqueKey == e.Presen
 083                    .Max(f => f.LastPlayedDate),
 084            (ItemSortBy.SeriesDatePlayed, null) => e =>
 085                jellyfinDbContext.UserData
 086                    .Where(w => w.Played && w.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
 087                    .Max(f => f.LastPlayedDate),
 5088            _ => e => e.SortName
 16689        };
 90    }
 91
 92    /// <summary>
 93    /// Creates an expression to order search results by match quality.
 94    /// Prioritizes: exact match (0) > prefix match with word boundary (1) > prefix match (2) > contains (3).
 95    /// Considers both CleanName and OriginalTitle for matching.
 96    /// </summary>
 97    /// <param name="searchTerm">The search term to match against.</param>
 98    /// <returns>An expression that returns an integer representing match quality (lower is better).</returns>
 99    public static Expression<Func<BaseItemEntity, int>> MapSearchRelevanceOrder(string searchTerm)
 100    {
 0101        var cleanSearchTerm = GetCleanValue(searchTerm);
 0102        var searchPrefix = cleanSearchTerm + " ";
 0103        var originalSearchLower = searchTerm.ToLowerInvariant();
 0104        var originalSearchPrefix = originalSearchLower + " ";
 0105        return e =>
 0106            // Exact match on CleanName or OriginalTitle
 0107            (e.CleanName == cleanSearchTerm || (e.OriginalTitle != null && e.OriginalTitle.ToLower() == originalSearchLo
 0108            // Prefix match with word boundary
 0109            (e.CleanName!.StartsWith(searchPrefix) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().StartsWith(o
 0110            // Prefix match
 0111            (e.CleanName!.StartsWith(cleanSearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().StartsWit
 112    }
 113
 114    private static string GetCleanValue(string value)
 115    {
 0116        if (string.IsNullOrWhiteSpace(value))
 117        {
 0118            return value;
 119        }
 120
 0121        return value.RemoveDiacritics().ToLowerInvariant();
 122    }
 123}