feat(fts): replace ILIKE hasText with FTS two-phase search and RELEVANCE sort
- DocumentSort: add RELEVANCE enum value - DocumentSpecifications: remove hasText() ILIKE, add hasIds(List<UUID>) for FTS-pre-filtered ID sets - DocumentService.searchDocuments(): FTS two-phase path — findRankedIdsByFts() returns ranked UUIDs, hasIds() narrows subsequent Specification query, in-memory re-sort preserves rank order; RELEVANCE is the default when text is present and no explicit non-relevance sort is requested - DocumentSpecificationsTest: remove hasText() tests (Specification removed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,6 @@ import org.raddatz.familienarchiv.config.FlywayConfig;
|
||||
import org.raddatz.familienarchiv.model.Document;
|
||||
import org.raddatz.familienarchiv.model.DocumentStatus;
|
||||
import org.raddatz.familienarchiv.model.Person;
|
||||
import org.raddatz.familienarchiv.model.PersonNameAlias;
|
||||
import org.raddatz.familienarchiv.model.PersonNameAliasType;
|
||||
import org.raddatz.familienarchiv.model.Tag;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
|
||||
@@ -30,7 +28,6 @@ class DocumentSpecificationsTest {
|
||||
|
||||
@Autowired DocumentRepository documentRepository;
|
||||
@Autowired PersonRepository personRepository;
|
||||
@Autowired PersonNameAliasRepository aliasRepository;
|
||||
@Autowired TagRepository tagRepository;
|
||||
|
||||
private Person sender;
|
||||
@@ -79,56 +76,6 @@ class DocumentSpecificationsTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
// ─── hasText ──────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void hasText_returnsAllDocuments_whenTextIsNull() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText(null)));
|
||||
assertThat(result).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_returnsAllDocuments_whenTextIsBlank() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText(" ")));
|
||||
assertThat(result).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_filtersOnTitle() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("familienfoto")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactly("Familienfoto");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_filtersOnOriginalFilename() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("brief_late")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactly("Neuerer Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_filtersOnTranscription() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("schreibe dir")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactly("Alter Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_filtersOnLocation() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("berlin")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactly("Alter Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_isCaseInsensitive() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("BRIEF")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactlyInAnyOrder("Alter Brief", "Neuerer Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_returnsEmpty_whenNoMatch() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("xyznotexist")));
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── hasSender ────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@@ -253,36 +200,6 @@ class DocumentSpecificationsTest {
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_findsByPartialSenderLastName() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("üller")));
|
||||
assertThat(result).extracting(Document::getTitle)
|
||||
.containsExactlyInAnyOrder("Alter Brief", "Neuerer Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_findsByPartialReceiverLastName() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("schmid")));
|
||||
assertThat(result).extracting(Document::getTitle).containsExactly("Alter Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_findsByPartialTagName() {
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("amili")));
|
||||
assertThat(result).extracting(Document::getTitle)
|
||||
.containsExactlyInAnyOrder("Alter Brief", "Familienfoto");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_doesNotProduceDuplicatesForDocumentWithMultipleReceivers() {
|
||||
Person receiver2 = personRepository.save(Person.builder().firstName("Karl").lastName("Schmidt").build());
|
||||
briefEarly.setReceivers(new java.util.HashSet<>(Set.of(receiver, receiver2)));
|
||||
documentRepository.save(briefEarly);
|
||||
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("schmid")));
|
||||
assertThat(result).hasSize(1);
|
||||
}
|
||||
|
||||
// ─── hasTagPartial ────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@@ -329,26 +246,4 @@ class DocumentSpecificationsTest {
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
// ─── hasText with aliases ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void hasText_findsDocumentBySenderAliasLastName() {
|
||||
aliasRepository.save(PersonNameAlias.builder()
|
||||
.person(sender).lastName("von Mueller").type(PersonNameAliasType.BIRTH).sortOrder(0).build());
|
||||
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("von Mueller")));
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
assertThat(result).extracting(Document::getTitle).contains("Alter Brief");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasText_findsDocumentByReceiverAliasLastName() {
|
||||
aliasRepository.save(PersonNameAlias.builder()
|
||||
.person(receiver).lastName("de Gruyter").type(PersonNameAliasType.BIRTH).sortOrder(0).build());
|
||||
|
||||
List<Document> result = documentRepository.findAll(Specification.where(hasText("de Gruyter")));
|
||||
|
||||
assertThat(result).isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user