fix string matching to be accent insensitive

This commit is contained in:
Gauthier Roebroeck 2026-02-12 16:22:21 +08:00
parent 57beffaf5c
commit 00ba413262
2 changed files with 32 additions and 31 deletions

View file

@ -1,6 +1,7 @@
package org.gotson.komga.infrastructure.jooq
import org.gotson.komga.domain.model.SearchOperator
import org.gotson.komga.language.stripAccents
import org.jooq.Field
import java.time.LocalDate
import java.time.ZoneOffset
@ -10,8 +11,8 @@ fun SearchOperator.Equality<String>.toCondition(
field: Field<String>,
ignoreCase: Boolean = false,
) = when (this) {
is SearchOperator.Is -> if (ignoreCase) field.equalIgnoreCase(this.value) else field.eq(this.value)
is SearchOperator.IsNot -> if (ignoreCase) field.notEqualIgnoreCase(this.value) else field.ne(this.value)
is SearchOperator.Is -> if (ignoreCase) field.unicode1().equal(this.value) else field.equal(this.value)
is SearchOperator.IsNot -> if (ignoreCase) field.unicode1().notEqual(this.value) else field.notEqual(this.value)
}
fun <T> SearchOperator.Equality<T>.toCondition(field: Field<T>) =
@ -30,14 +31,14 @@ fun <T> SearchOperator.Equality<T>.toCondition(
fun SearchOperator.StringOp.toCondition(field: Field<String>) =
when (this) {
is SearchOperator.BeginsWith -> field.startsWithIgnoreCase(value)
is SearchOperator.DoesNotBeginWith -> field.startsWithIgnoreCase(value).not()
is SearchOperator.EndsWith -> field.endsWithIgnoreCase(value)
is SearchOperator.DoesNotEndWith -> field.endsWithIgnoreCase(value).not()
is SearchOperator.Contains -> field.containsIgnoreCase(value)
is SearchOperator.DoesNotContain -> field.notContainsIgnoreCase(value)
is SearchOperator.Is<*> -> field.equalIgnoreCase(value as String)
is SearchOperator.IsNot<*> -> field.notEqualIgnoreCase(value as String)
is SearchOperator.BeginsWith -> field.udfStripAccents().startsWithIgnoreCase(value.stripAccents())
is SearchOperator.DoesNotBeginWith -> field.udfStripAccents().startsWithIgnoreCase(value.stripAccents()).not()
is SearchOperator.EndsWith -> field.udfStripAccents().endsWithIgnoreCase(value.stripAccents())
is SearchOperator.DoesNotEndWith -> field.udfStripAccents().endsWithIgnoreCase(value.stripAccents()).not()
is SearchOperator.Contains -> field.udfStripAccents().containsIgnoreCase(value.stripAccents())
is SearchOperator.DoesNotContain -> field.udfStripAccents().notContainsIgnoreCase(value.stripAccents())
is SearchOperator.Is<*> -> field.unicode1().equal(value as String)
is SearchOperator.IsNot<*> -> field.unicode1().notEqual(value as String)
}
fun SearchOperator.Date.toCondition(field: Field<LocalDate>) =

View file

@ -649,7 +649,7 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Publisher(SearchOperator.Is("marvelOUS")))
val search = SeriesSearch(SearchCondition.Publisher(SearchOperator.Is("mÂrvelOUS")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -658,7 +658,7 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Publisher(SearchOperator.IsNot("marvelOUS")))
val search = SeriesSearch(SearchCondition.Publisher(SearchOperator.IsNot("mÂrvelOUS")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1201,12 +1201,12 @@ class SeriesSearchTest(
makeSeries("2", library1.id).let { series ->
seriesLifecycle.createSeries(series)
seriesMetadataRepository.findById(series.id).let {
seriesMetadataRepository.update(it.copy(title = "Series 2"))
seriesMetadataRepository.update(it.copy(title = "Series two"))
}
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.Is("seRIES 1")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.Is("séRIES 1")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1215,7 +1215,7 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.IsNot("series 1")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.IsNot("séRIES 1")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1224,7 +1224,7 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.Contains("series")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.Contains("séRIES")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1233,16 +1233,16 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotContain("1")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotContain("TWÔ")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
assertThat(found.map { it.name }).containsExactlyInAnyOrder("2")
assertThat(foundDto.map { it.name }).containsExactlyInAnyOrder("2")
assertThat(found.map { it.name }).containsExactlyInAnyOrder("1")
assertThat(foundDto.map { it.name }).containsExactlyInAnyOrder("1")
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.BeginsWith("series")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.BeginsWith("séRIES")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1251,7 +1251,7 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotBeginWith("series")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotBeginWith("séRIES")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
@ -1260,22 +1260,22 @@ class SeriesSearchTest(
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.EndsWith("1")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
assertThat(found.map { it.name }).containsExactlyInAnyOrder("1")
assertThat(foundDto.map { it.name }).containsExactlyInAnyOrder("1")
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotEndWith("1")))
val search = SeriesSearch(SearchCondition.Title(SearchOperator.EndsWith("TWÔ")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
assertThat(found.map { it.name }).containsExactlyInAnyOrder("2")
assertThat(foundDto.map { it.name }).containsExactlyInAnyOrder("2")
}
run {
val search = SeriesSearch(SearchCondition.Title(SearchOperator.DoesNotEndWith("TWÔ")))
val found = seriesDao.findAll(search.condition, SearchContext(user1), Pageable.unpaged()).content
val foundDto = seriesDtoDao.findAll(search, SearchContext(user1), Pageable.unpaged()).content
assertThat(found.map { it.name }).containsExactlyInAnyOrder("1")
assertThat(foundDto.map { it.name }).containsExactlyInAnyOrder("1")
}
}
@Test