Merge pull request #17757 from felixfoertsch/fix/legacy-filter-tag-query
Fix UI stalls caused by large tag and genre sets
This commit is contained in:
@@ -121,20 +121,22 @@ public sealed partial class BaseItemRepository
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
|
||||
var query = context.ItemValuesMap
|
||||
.AsNoTracking()
|
||||
.Where(e => itemValueTypes.Any(w => w == e.ItemValue.Type));
|
||||
var maps = context.ItemValuesMap.AsNoTracking();
|
||||
if (withItemTypes.Count > 0)
|
||||
{
|
||||
query = query.Where(e => withItemTypes.Contains(e.Item.Type));
|
||||
maps = maps.Where(e => withItemTypes.Contains(e.Item.Type));
|
||||
}
|
||||
|
||||
if (excludeItemTypes.Count > 0)
|
||||
{
|
||||
query = query.Where(e => !excludeItemTypes.Contains(e.Item.Type));
|
||||
maps = maps.Where(e => !excludeItemTypes.Contains(e.Item.Type));
|
||||
}
|
||||
|
||||
return query.Select(e => e.ItemValue)
|
||||
return context.ItemValues
|
||||
.AsNoTracking()
|
||||
.WhereOneOrMany(itemValueTypes, e => e.Type)
|
||||
.Where(e => maps.Any(m => m.ItemValueId == e.ItemValueId))
|
||||
.Select(e => new { e.CleanValue, e.Value })
|
||||
.GroupBy(e => e.CleanValue)
|
||||
.Select(g => g.Min(v => v.Value)!)
|
||||
.ToArray();
|
||||
|
||||
@@ -626,18 +626,26 @@ public sealed partial class BaseItemRepository
|
||||
.ToArray();
|
||||
|
||||
var tags = context.ItemValuesMap
|
||||
.Where(ivm => ivm.ItemValue.Type == ItemValueType.Tags)
|
||||
.Where(ivm => matchingItemIds.Contains(ivm.ItemId))
|
||||
.Select(ivm => ivm.ItemValue)
|
||||
.Join(
|
||||
context.ItemValues,
|
||||
ivm => ivm.ItemValueId,
|
||||
iv => iv.ItemValueId,
|
||||
(ivm, iv) => new { ivm.ItemId, iv.Type, iv.CleanValue, iv.Value })
|
||||
.Where(iv => iv.Type == ItemValueType.Tags)
|
||||
.Where(iv => matchingItemIds.Contains(iv.ItemId))
|
||||
.GroupBy(iv => iv.CleanValue)
|
||||
.Select(g => g.Min(iv => iv.Value))
|
||||
.OrderBy(t => t)
|
||||
.ToArray();
|
||||
|
||||
var genres = context.ItemValuesMap
|
||||
.Where(ivm => ivm.ItemValue.Type == ItemValueType.Genre)
|
||||
.Where(ivm => matchingItemIds.Contains(ivm.ItemId))
|
||||
.Select(ivm => ivm.ItemValue)
|
||||
.Join(
|
||||
context.ItemValues,
|
||||
ivm => ivm.ItemValueId,
|
||||
iv => iv.ItemValueId,
|
||||
(ivm, iv) => new { ivm.ItemId, iv.Type, iv.CleanValue, iv.Value })
|
||||
.Where(iv => iv.Type == ItemValueType.Genre)
|
||||
.Where(iv => matchingItemIds.Contains(iv.ItemId))
|
||||
.GroupBy(iv => iv.CleanValue)
|
||||
.Select(g => g.Min(iv => iv.Value))
|
||||
.OrderBy(g => g)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Database.Implementations.Entities;
|
||||
using Jellyfin.Server.Implementations.Item;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using Xunit;
|
||||
using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Tests.Item;
|
||||
|
||||
public sealed class BaseItemRepositoryItemValueTests : SqliteDbTestFixture
|
||||
{
|
||||
private readonly BaseItemRepository _repository;
|
||||
private readonly string _audioTypeName;
|
||||
private readonly string _movieTypeName;
|
||||
|
||||
public BaseItemRepositoryItemValueTests()
|
||||
{
|
||||
var itemTypeLookup = new ItemTypeLookup();
|
||||
_audioTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Audio];
|
||||
_movieTypeName = itemTypeLookup.BaseItemKindNames[BaseItemKind.Movie];
|
||||
_repository = CreateBaseItemRepository(itemTypeLookup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetQueryFiltersLegacy_GroupsAndFiltersItemValues()
|
||||
{
|
||||
var firstItem = CreateMovieEntity(Guid.NewGuid(), "First");
|
||||
var secondItem = CreateMovieEntity(Guid.NewGuid(), "Second");
|
||||
var excludedItem = new BaseItemEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = _audioTypeName,
|
||||
Name = "Excluded Audio",
|
||||
MediaType = "Audio",
|
||||
IsMovie = false,
|
||||
IsFolder = false,
|
||||
IsVirtualItem = false
|
||||
};
|
||||
var firstTag = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Tags,
|
||||
Value = "Alpha",
|
||||
CleanValue = "alpha"
|
||||
};
|
||||
var duplicateTag = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Tags,
|
||||
Value = "alpha",
|
||||
CleanValue = "alpha"
|
||||
};
|
||||
var secondTag = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Tags,
|
||||
Value = "Beta",
|
||||
CleanValue = "beta"
|
||||
};
|
||||
var genre = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Genre,
|
||||
Value = "Genre Leak",
|
||||
CleanValue = "genre leak"
|
||||
};
|
||||
var excludedTag = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Tags,
|
||||
Value = "Excluded Tag",
|
||||
CleanValue = "excluded tag"
|
||||
};
|
||||
var excludedGenre = new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = ItemValueType.Genre,
|
||||
Value = "Excluded Genre",
|
||||
CleanValue = "excluded genre"
|
||||
};
|
||||
|
||||
using (var context = CreateDbContext())
|
||||
{
|
||||
context.BaseItems.AddRange(firstItem, secondItem, excludedItem);
|
||||
context.ItemValues.AddRange(firstTag, duplicateTag, secondTag, genre, excludedTag, excludedGenre);
|
||||
context.ItemValuesMap.AddRange(
|
||||
CreateMap(firstItem, firstTag),
|
||||
CreateMap(firstItem, duplicateTag),
|
||||
CreateMap(secondItem, secondTag),
|
||||
CreateMap(firstItem, genre),
|
||||
CreateMap(excludedItem, excludedTag),
|
||||
CreateMap(excludedItem, excludedGenre));
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
var result = _repository.GetQueryFiltersLegacy(new InternalItemsQuery(new Database.Implementations.Entities.User("test", "auth", "reset"))
|
||||
{
|
||||
IncludeItemTypes = [BaseItemKind.Movie]
|
||||
});
|
||||
|
||||
Assert.Equal(["Alpha", "Beta"], result.Tags);
|
||||
Assert.Equal(["Genre Leak"], result.Genres);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetGenreNames_GroupsAndFiltersMappedItemValues()
|
||||
{
|
||||
var movie = CreateMovieEntity(Guid.NewGuid(), "Movie");
|
||||
var audio = new BaseItemEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = _audioTypeName,
|
||||
Name = "Audio",
|
||||
MediaType = "Audio",
|
||||
IsFolder = false,
|
||||
IsVirtualItem = false
|
||||
};
|
||||
var movieGenre = CreateItemValue(ItemValueType.Genre, "Movie Genre", "movie genre");
|
||||
var duplicateMovieGenre = CreateItemValue(ItemValueType.Genre, "movie genre", "movie genre");
|
||||
var musicGenre = CreateItemValue(ItemValueType.Genre, "Music Genre", "music genre");
|
||||
var orphanedGenre = CreateItemValue(ItemValueType.Genre, "Orphaned Genre", "orphaned genre");
|
||||
|
||||
using (var context = CreateDbContext())
|
||||
{
|
||||
context.BaseItems.AddRange(movie, audio);
|
||||
context.ItemValues.AddRange(movieGenre, duplicateMovieGenre, musicGenre, orphanedGenre);
|
||||
context.ItemValuesMap.AddRange(
|
||||
CreateMap(movie, movieGenre),
|
||||
CreateMap(movie, duplicateMovieGenre),
|
||||
CreateMap(audio, musicGenre));
|
||||
context.SaveChanges();
|
||||
}
|
||||
|
||||
Assert.Equal(["Movie Genre"], _repository.GetGenreNames());
|
||||
Assert.Equal(["Music Genre"], _repository.GetMusicGenreNames());
|
||||
}
|
||||
|
||||
private BaseItemEntity CreateMovieEntity(Guid id, string name)
|
||||
{
|
||||
return new BaseItemEntity
|
||||
{
|
||||
Id = id,
|
||||
Type = _movieTypeName,
|
||||
Name = name,
|
||||
MediaType = "Video",
|
||||
IsMovie = true,
|
||||
IsFolder = false,
|
||||
IsVirtualItem = false
|
||||
};
|
||||
}
|
||||
|
||||
private static ItemValueMap CreateMap(BaseItemEntity item, ItemValue itemValue)
|
||||
{
|
||||
return new ItemValueMap
|
||||
{
|
||||
ItemId = item.Id,
|
||||
ItemValueId = itemValue.ItemValueId,
|
||||
Item = item,
|
||||
ItemValue = itemValue
|
||||
};
|
||||
}
|
||||
|
||||
private static ItemValue CreateItemValue(ItemValueType type, string value, string cleanValue)
|
||||
{
|
||||
return new ItemValue
|
||||
{
|
||||
ItemValueId = Guid.NewGuid(),
|
||||
Type = type,
|
||||
Value = value,
|
||||
CleanValue = cleanValue
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user