From 37fd265add7751b1c5c27f30140b0926ffd16155 Mon Sep 17 00:00:00 2001 From: excelsior <44299346+excelsior-dev@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:38:46 +0200 Subject: [PATCH 1/4] add SortName column to search query --- .../Library/Search/SqlSearchProvider.cs | 15 +++++++++++++-- .../Library/SqlSearchProviderTests.cs | 10 +++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs index 2cbfb6a4fa..7f0828c19f 100644 --- a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs +++ b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs @@ -10,6 +10,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Extensions; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -36,6 +37,7 @@ public class SqlSearchProvider : IInternalSearchProvider private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IItemQueryHelpers _queryHelpers; + private readonly IServerConfigurationManager _configurationManager; /// /// Initializes a new instance of the class. @@ -45,18 +47,21 @@ public class SqlSearchProvider : IInternalSearchProvider /// The library manager. /// The user manager. /// The shared item query helpers. + /// The configuration manager. public SqlSearchProvider( IDbContextFactory dbProvider, IItemTypeLookup itemTypeLookup, ILibraryManager libraryManager, IUserManager userManager, - IItemQueryHelpers queryHelpers) + IItemQueryHelpers queryHelpers, + IServerConfigurationManager configurationManager) { _dbProvider = dbProvider; _itemTypeLookup = itemTypeLookup; _libraryManager = libraryManager; _userManager = userManager; _queryHelpers = queryHelpers; + _configurationManager = configurationManager; } /// @@ -98,6 +103,11 @@ public class SqlSearchProvider : IInternalSearchProvider // so match it via a case-insensitive LIKE rather than a per-row case conversion // that may not translate to SQL on every provider. var likeOriginal = $"%{rawSearchTerm}%"; + + // great benefit for users with metadata in non-latin script, + // since SortName is among other things stored transliterated + var sortNameShape = BaseItem.GetSortName(rawSearchTerm, true, _configurationManager.Configuration); + var limit = query.Limit ?? DefaultSearchLimit; var dbContext = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false); @@ -109,7 +119,8 @@ public class SqlSearchProvider : IInternalSearchProvider .Where(e => e.Id != _placeholderId) .Where(e => !e.IsVirtualItem) .Where(e => e.CleanName!.Contains(cleanSearchTerm) - || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal))); + || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal)) + || (e.SortName != null && e.SortName.Contains(sortNameShape))); dbQuery = ApplyTypeFilter(dbQuery, query.IncludeItemTypes, query.ExcludeItemTypes); dbQuery = ApplyMediaTypeFilter(dbQuery, query.MediaTypes); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/SqlSearchProviderTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/SqlSearchProviderTests.cs index 5aa770b9b3..89ca037256 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/SqlSearchProviderTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/SqlSearchProviderTests.cs @@ -9,8 +9,11 @@ using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations.Entities; using Jellyfin.Server.Implementations.Item; using Jellyfin.Server.Implementations.Tests.Item; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Configuration; +using Microsoft.Extensions.Configuration; using Moq; using Xunit; using BaseItemKind = Jellyfin.Data.Enums.BaseItemKind; @@ -52,12 +55,17 @@ public sealed class SqlSearchProviderTests : SqliteDbTestFixture var userManager = new Mock(); userManager.Setup(u => u.GetUserById(_user.Id)).Returns(_user); + var serverConfigurationManager = new Mock(); + serverConfigurationManager.Setup(scm => scm.Configuration) + .Returns(new ServerConfiguration()); + _provider = new SqlSearchProvider( CreateDbContextFactory(), itemTypeLookup, _libraryManager.Object, userManager.Object, - CreateBaseItemRepository(itemTypeLookup)); + CreateBaseItemRepository(itemTypeLookup), + serverConfigurationManager.Object); } [Fact] From cf7beff022f5e7c164217e6eb5110ff2faa6333f Mon Sep 17 00:00:00 2001 From: excelsior <44299346+excelsior-dev@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:48:33 +0200 Subject: [PATCH 2/4] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2e9c71c205..17b201a0af 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -243,6 +243,7 @@ - [martin-77](https://github.com/martin-77) - [Oggeb1](https://github.com/Oggeb1) - [Mateus Bandeira](https://github.com/mateusbandeiraa) + - [excelsior](https://github.com/excelsior-dev) # Emby Contributors From a5427dc8761c7788db4dbc2b2dd1523b6499be5f Mon Sep 17 00:00:00 2001 From: excelsior <44299346+excelsior-dev@users.noreply.github.com> Date: Tue, 29 Sep 2026 14:55:49 +0200 Subject: [PATCH 3/4] Ensure sortNameShape is not empty and use SQL LIKE --- .../Library/Search/SqlSearchProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs index 7f0828c19f..ab43cd7c78 100644 --- a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs +++ b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs @@ -107,6 +107,7 @@ public class SqlSearchProvider : IInternalSearchProvider // great benefit for users with metadata in non-latin script, // since SortName is among other things stored transliterated var sortNameShape = BaseItem.GetSortName(rawSearchTerm, true, _configurationManager.Configuration); + var likeSortName = $"%{sortNameShape}%"; var limit = query.Limit ?? DefaultSearchLimit; @@ -120,7 +121,7 @@ public class SqlSearchProvider : IInternalSearchProvider .Where(e => !e.IsVirtualItem) .Where(e => e.CleanName!.Contains(cleanSearchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal)) - || (e.SortName != null && e.SortName.Contains(sortNameShape))); + || (!string.IsNullOrEmpty(sortNameShape) && e.SortName != null && EF.Functions.Like(e.SortName, likeSortName))); dbQuery = ApplyTypeFilter(dbQuery, query.IncludeItemTypes, query.ExcludeItemTypes); dbQuery = ApplyMediaTypeFilter(dbQuery, query.MediaTypes); From 514b7e390c20421a7f4d5bdbd7199dabaa38c634 Mon Sep 17 00:00:00 2001 From: excelsior <44299346+excelsior-dev@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:12:19 +0200 Subject: [PATCH 4/4] Evaluate constant outside of query expression --- .../Library/Search/SqlSearchProvider.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs index ab43cd7c78..a5ea02df51 100644 --- a/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs +++ b/Emby.Server.Implementations/Library/Search/SqlSearchProvider.cs @@ -104,10 +104,10 @@ public class SqlSearchProvider : IInternalSearchProvider // that may not translate to SQL on every provider. var likeOriginal = $"%{rawSearchTerm}%"; - // great benefit for users with metadata in non-latin script, - // since SortName is among other things stored transliterated + // Great benefit for users with metadata in non-latin script + // since SortName is among other things stored transliterated. var sortNameShape = BaseItem.GetSortName(rawSearchTerm, true, _configurationManager.Configuration); - var likeSortName = $"%{sortNameShape}%"; + var likeSortName = string.IsNullOrWhiteSpace(sortNameShape) ? null : $"%{sortNameShape}%"; var limit = query.Limit ?? DefaultSearchLimit; @@ -118,10 +118,21 @@ public class SqlSearchProvider : IInternalSearchProvider var dbQuery = dbContext.BaseItems .AsNoTracking() .Where(e => e.Id != _placeholderId) - .Where(e => !e.IsVirtualItem) - .Where(e => e.CleanName!.Contains(cleanSearchTerm) - || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal)) - || (!string.IsNullOrEmpty(sortNameShape) && e.SortName != null && EF.Functions.Like(e.SortName, likeSortName))); + .Where(e => !e.IsVirtualItem); + + if (likeSortName is null) + { + dbQuery = dbQuery + .Where(e => e.CleanName!.Contains(cleanSearchTerm) + || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal))); + } + else + { + dbQuery = dbQuery + .Where(e => e.CleanName!.Contains(cleanSearchTerm) + || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle, likeOriginal)) + || (e.SortName != null && EF.Functions.Like(e.SortName, likeSortName))); + } dbQuery = ApplyTypeFilter(dbQuery, query.IncludeItemTypes, query.ExcludeItemTypes); dbQuery = ApplyMediaTypeFilter(dbQuery, query.MediaTypes);