aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs1
-rw-r--r--Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs12
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Item/ArtistNameLookupTests.cs173
4 files changed, 175 insertions, 13 deletions
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index caba304888..4c468569bc 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1354,7 +1354,6 @@ namespace Emby.Server.Implementations.Library
{
IncludeItemTypes = [BaseItemKind.MusicArtist],
Name = name,
- UseRawName = true,
DtoOptions = options
}).Cast<MusicArtist>()
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
index d726f0f143..a7110485b4 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs
@@ -476,16 +476,8 @@ public sealed partial class BaseItemRepository
if (!string.IsNullOrWhiteSpace(filter.Name))
{
- if (filter.UseRawName == true)
- {
- var nameLower = filter.Name.ToLowerInvariant();
- baseQuery = baseQuery.Where(e => e.Name!.ToLower() == nameLower);
- }
- else
- {
- var cleanName = filter.Name.GetCleanValue();
- baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
- }
+ var cleanName = filter.Name.GetCleanValue();
+ baseQuery = baseQuery.Where(e => e.CleanName == cleanName);
}
var nameContains = filter.NameContains;
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 7c88d5dd05..e0f0e0d476 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -235,8 +235,6 @@ namespace MediaBrowser.Controller.Entities
public string? Name { get; set; }
- public bool? UseRawName { get; set; }
-
public string? Person { get; set; }
public Guid[] PersonIds { get; set; }
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Item/ArtistNameLookupTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Item/ArtistNameLookupTests.cs
new file mode 100644
index 0000000000..0c1d453293
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Item/ArtistNameLookupTests.cs
@@ -0,0 +1,173 @@
+using System;
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Linq;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Naming.Common;
+using Emby.Server.Implementations.Data;
+using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Extensions;
+using Jellyfin.Server.Implementations.Item;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Configuration;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Moq;
+using Xunit;
+using ServerLibraryManager = Emby.Server.Implementations.Library.LibraryManager;
+
+namespace Jellyfin.Server.Implementations.Tests.Item;
+
+public sealed class ArtistNameLookupTests : SqliteDbTestFixture
+{
+ private readonly CommandRecorder _recorder;
+
+ public ArtistNameLookupTests()
+ : this(new CommandRecorder())
+ {
+ }
+
+ private ArtistNameLookupTests(CommandRecorder recorder)
+ : base(recorder)
+ {
+ _recorder = recorder;
+ }
+
+ [Theory]
+ [InlineData("Björk", "bjork")]
+ [InlineData("AC/DC", "ac dc")]
+ [InlineData("An Artist", " AN ARTIST ")]
+ [InlineData("Artist", "ARTIST")]
+ public void GetArtist_UsesSameNormalizedNameAsFindArtists(string storedName, string requestedName)
+ {
+ var lookup = new ItemTypeLookup();
+ var artistId = Guid.NewGuid();
+ using (var context = CreateDbContext())
+ {
+ context.BaseItems.AddRange(
+ new BaseItemEntity
+ {
+ Id = artistId,
+ Name = storedName,
+ CleanName = storedName.GetCleanValue(),
+ Type = lookup.BaseItemKindNames[BaseItemKind.MusicArtist]
+ },
+ new BaseItemEntity
+ {
+ Id = Guid.NewGuid(),
+ Name = storedName,
+ CleanName = storedName.GetCleanValue(),
+ Type = lookup.BaseItemKindNames[BaseItemKind.Person]
+ });
+ context.SaveChanges();
+ }
+
+ var manager = CreateLibraryManager(lookup);
+ _recorder.Commands.Clear();
+
+ Assert.Equal(artistId, manager.GetArtist(requestedName).Id);
+ var query = Assert.Single(_recorder.Commands, c => c.Sql.Contains("\"CleanName\" =", StringComparison.Ordinal));
+ Assert.Contains(Explain(query), line => line.Contains("IX_BaseItems_Type_CleanName (Type=? AND CleanName=?)", StringComparison.Ordinal));
+ Assert.Equal(artistId, Assert.Single(manager.GetArtists([requestedName])[requestedName]).Id);
+ }
+
+ [Fact]
+ public void GetArtist_PrefersFilesystemArtistWhenNormalizedNamesMatch()
+ {
+ var lookup = new ItemTypeLookup();
+ var parentId = Guid.NewGuid();
+ var artistId = Guid.NewGuid();
+ using (var context = CreateDbContext())
+ {
+ context.BaseItems.AddRange(
+ new BaseItemEntity
+ {
+ Id = parentId,
+ Name = "Music",
+ Type = lookup.BaseItemKindNames[BaseItemKind.Folder]
+ },
+ new BaseItemEntity
+ {
+ Id = Guid.NewGuid(),
+ Name = "Bjork",
+ CleanName = "Bjork".GetCleanValue(),
+ Type = lookup.BaseItemKindNames[BaseItemKind.MusicArtist]
+ },
+ new BaseItemEntity
+ {
+ Id = artistId,
+ ParentId = parentId,
+ Name = "Björk",
+ CleanName = "Björk".GetCleanValue(),
+ Type = lookup.BaseItemKindNames[BaseItemKind.MusicArtist]
+ });
+ context.SaveChanges();
+ }
+
+ Assert.Equal(artistId, CreateLibraryManager(lookup).GetArtist("Bjork").Id);
+ }
+
+ private ServerLibraryManager CreateLibraryManager(ItemTypeLookup lookup)
+ {
+ var repository = CreateBaseItemRepository(lookup);
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ fixture.Register(() => new NamingOptions());
+ var configuration = fixture.Freeze<Mock<IServerConfigurationManager>>();
+ configuration.Setup(c => c.Configuration).Returns(new ServerConfiguration());
+ configuration.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ fixture.Inject<IItemRepository>(repository);
+ fixture.Inject<ILinkedChildrenService>(new LinkedChildrenService(CreateDbContextFactory(), lookup, repository));
+ return fixture.Create<ServerLibraryManager>();
+ }
+
+ private string[] Explain(RecordedCommand query)
+ {
+ using var context = CreateDbContext();
+ using var command = context.Database.GetDbConnection().CreateCommand();
+#pragma warning disable CA2100 // query.Sql is generated by EF Core; query values remain bound parameters.
+ command.CommandText = "EXPLAIN QUERY PLAN " + query.Sql;
+#pragma warning restore CA2100
+ foreach (var value in query.Parameters)
+ {
+ var parameter = command.CreateParameter();
+ parameter.ParameterName = value.Name;
+ parameter.Value = value.Value;
+ command.Parameters.Add(parameter);
+ }
+
+ using var reader = command.ExecuteReader();
+ var plan = new List<string>();
+ while (reader.Read())
+ {
+ plan.Add(reader.GetString(3));
+ }
+
+ return plan.ToArray();
+ }
+
+ private sealed record RecordedCommand(string Sql, (string Name, object? Value)[] Parameters);
+
+ private sealed class CommandRecorder : DbCommandInterceptor
+ {
+ public List<RecordedCommand> Commands { get; } = [];
+
+ public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
+ {
+ Record(command);
+ return result;
+ }
+
+ public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
+ {
+ Record(command);
+ return result;
+ }
+
+ private void Record(DbCommand command) => Commands.Add(new RecordedCommand(
+ command.CommandText,
+ command.Parameters.Cast<DbParameter>().Select(p => (p.ParameterName, p.Value)).ToArray()));
+ }
+}