fix(search): handle null firstName in all search queries

Use COALESCE to convert null firstName to empty string in:
- PersonRepository.searchByName (JPQL)
- PersonRepository.searchWithDocumentCount (native SQL)
- PersonRepository.findCorrespondentsWithFilter (native SQL)
- DocumentSpecifications.hasText (Criteria API, sender + receiver)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-08 11:59:41 +02:00
parent 92f1a112f5
commit de2cc677a9
3 changed files with 30 additions and 8 deletions

View File

@@ -41,7 +41,7 @@ public class DocumentSpecifications {
cb.equal(receiverRoot.get("id"), root.get("id")),
cb.or(
cb.like(cb.lower(receiverJoin.get("lastName")), likePattern),
cb.like(cb.lower(receiverJoin.get("firstName")), likePattern)
cb.like(cb.lower(cb.coalesce(receiverJoin.get("firstName"), "")), likePattern)
)
);
@@ -74,7 +74,7 @@ public class DocumentSpecifications {
cb.like(cb.lower(root.get("transcription")), likePattern),
cb.like(cb.lower(root.get("location")), likePattern),
cb.like(cb.lower(senderJoin.get("lastName")), likePattern),
cb.like(cb.lower(senderJoin.get("firstName")), likePattern),
cb.like(cb.lower(cb.coalesce(senderJoin.get("firstName"), "")), likePattern),
cb.like(cb.lower(senderAliasJoin.get("lastName")), likePattern),
cb.exists(receiverSub),
cb.exists(receiverAliasSub),

View File

@@ -16,8 +16,8 @@ import org.springframework.stereotype.Repository;
public interface PersonRepository extends JpaRepository<Person, UUID> {
@Query("SELECT DISTINCT p FROM Person p LEFT JOIN p.nameAliases a WHERE " +
"LOWER(CONCAT(p.firstName,' ',p.lastName)) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(CONCAT(p.lastName, ' ', p.firstName)) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(CONCAT(COALESCE(p.firstName, ''),' ',p.lastName)) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(CONCAT(p.lastName, ' ', COALESCE(p.firstName, ''))) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(p.alias) LIKE LOWER(CONCAT('%', :query, '%')) OR " +
"LOWER(a.lastName) LIKE LOWER(CONCAT('%', :query, '%')) " +
"ORDER BY p.lastName ASC, p.firstName ASC")
@@ -52,8 +52,8 @@ public interface PersonRepository extends JpaRepository<Person, UUID> {
+ (SELECT COUNT(*) FROM document_receivers dr WHERE dr.person_id = p.id) AS documentCount
FROM persons p
LEFT JOIN person_name_aliases a ON a.person_id = p.id
WHERE LOWER(CONCAT(p.first_name,' ',p.last_name)) LIKE LOWER(CONCAT('%',:query,'%'))
OR LOWER(CONCAT(p.last_name,' ',p.first_name)) LIKE LOWER(CONCAT('%',:query,'%'))
WHERE LOWER(CONCAT(COALESCE(p.first_name,''),' ',p.last_name)) LIKE LOWER(CONCAT('%',:query,'%'))
OR LOWER(CONCAT(p.last_name,' ',COALESCE(p.first_name,''))) LIKE LOWER(CONCAT('%',:query,'%'))
OR LOWER(p.alias) LIKE LOWER(CONCAT('%',:query,'%'))
OR LOWER(a.last_name) LIKE LOWER(CONCAT('%',:query,'%'))
GROUP BY p.id, p.first_name, p.last_name, p.alias, p.birth_year, p.death_year, p.notes
@@ -98,8 +98,8 @@ public interface PersonRepository extends JpaRepository<Person, UUID> {
WHERE dr.person_id = :personId AND d.sender_id IS NOT NULL
) shared ON shared.other_id = p.id
WHERE p.id != :personId
AND (LOWER(CONCAT(p.first_name,' ',p.last_name)) LIKE LOWER(CONCAT('%',:q,'%'))
OR LOWER(CONCAT(p.last_name,' ',p.first_name)) LIKE LOWER(CONCAT('%',:q,'%'))
AND (LOWER(CONCAT(COALESCE(p.first_name,''),' ',p.last_name)) LIKE LOWER(CONCAT('%',:q,'%'))
OR LOWER(CONCAT(p.last_name,' ',COALESCE(p.first_name,''))) LIKE LOWER(CONCAT('%',:q,'%'))
OR LOWER(p.alias) LIKE LOWER(CONCAT('%',:q,'%')))
GROUP BY p.id
ORDER BY COUNT(DISTINCT shared.doc_id) DESC

View File

@@ -440,4 +440,26 @@ class PersonRepositoryTest {
assertThat(results).hasSize(1);
assertThat(results.get(0).getLastName()).isEqualTo("Cram");
}
// ─── null firstName handling ────────────────────────────────────────────
@Test
void searchByName_findsPersonWithNullFirstName() {
personRepository.save(Person.builder().lastName("Gesellschafter des Verlages").build());
List<Person> result = personRepository.searchByName("Gesellschafter");
assertThat(result).hasSize(1);
assertThat(result.get(0).getLastName()).isEqualTo("Gesellschafter des Verlages");
}
@Test
void searchWithDocumentCount_findsPersonWithNullFirstName() {
personRepository.save(Person.builder().lastName("Gesellschafter des Verlages").build());
List<PersonSummaryDTO> result = personRepository.searchWithDocumentCount("Gesellschafter");
assertThat(result).hasSize(1);
assertThat(result.get(0).getLastName()).isEqualTo("Gesellschafter des Verlages");
}
}