fix(document): order undated documents last on the DATE sort fast path
resolveSort produced Sort.by(direction, "documentDate") with NATIVE null handling, so Postgres surfaced undated (null meta_date) documents FIRST on an ASC sort. Apply nullsLast() so undated rows order last for both ASC and DESC, with a createdAt-asc tiebreaker for a stable total order when every row is null-dated (the upcoming "Nur undatierte" filter). Refs #668
This commit is contained in:
@@ -797,10 +797,17 @@ public class DocumentService {
|
||||
return transcriptionBlockQueryService.getCompletionStats(docIds);
|
||||
}
|
||||
|
||||
private Sort resolveSort(DocumentSort sort, String dir) {
|
||||
Sort resolveSort(DocumentSort sort, String dir) {
|
||||
Sort.Direction direction = "ASC".equalsIgnoreCase(dir) ? Sort.Direction.ASC : Sort.Direction.DESC;
|
||||
if (sort == null || sort == DocumentSort.DATE || sort == DocumentSort.RELEVANCE) {
|
||||
return Sort.by(direction, "documentDate");
|
||||
// Undated documents (null documentDate) must order last regardless of
|
||||
// direction — Postgres puts NULLs FIRST on ASC by default, which would
|
||||
// surface the undated pile at the top with no explanation (issue #668).
|
||||
// The createdAt tiebreaker gives a stable total order when every row is
|
||||
// null-dated (the "Nur undatierte" filter), so pagination is deterministic.
|
||||
return Sort.by(
|
||||
new Sort.Order(direction, "documentDate").nullsLast(),
|
||||
Sort.Order.asc("createdAt"));
|
||||
}
|
||||
// SENDER and RECEIVER are sorted in-memory before this method is called
|
||||
return switch (sort) {
|
||||
|
||||
@@ -1450,6 +1450,42 @@ class DocumentServiceTest {
|
||||
assertThat(result.items()).hasSize(1); // only the slice is enriched
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchDocuments_dateSort_DESC_ordersUndatedLast() {
|
||||
ArgumentCaptor<Pageable> captor = ArgumentCaptor.forClass(Pageable.class);
|
||||
when(documentRepository.findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Pageable.class)))
|
||||
.thenReturn(new PageImpl<>(List.of()));
|
||||
|
||||
documentService.searchDocuments(null, null, null, null, null, null, null, null,
|
||||
DocumentSort.DATE, "DESC", null,
|
||||
org.springframework.data.domain.PageRequest.of(0, 5));
|
||||
|
||||
verify(documentRepository).findAll(any(org.springframework.data.jpa.domain.Specification.class), captor.capture());
|
||||
Sort.Order dateOrder = captor.getValue().getSort().getOrderFor("documentDate");
|
||||
assertThat(dateOrder).isNotNull();
|
||||
assertThat(dateOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(dateOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NULLS_LAST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchDocuments_dateSort_ASC_ordersUndatedLast() {
|
||||
// The ASC bug: Postgres puts NULLs FIRST on ascending sort without explicit
|
||||
// NULLS LAST, surfacing undated documents at the top. This is the red.
|
||||
ArgumentCaptor<Pageable> captor = ArgumentCaptor.forClass(Pageable.class);
|
||||
when(documentRepository.findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Pageable.class)))
|
||||
.thenReturn(new PageImpl<>(List.of()));
|
||||
|
||||
documentService.searchDocuments(null, null, null, null, null, null, null, null,
|
||||
DocumentSort.DATE, "ASC", null,
|
||||
org.springframework.data.domain.PageRequest.of(0, 5));
|
||||
|
||||
verify(documentRepository).findAll(any(org.springframework.data.jpa.domain.Specification.class), captor.capture());
|
||||
Sort.Order dateOrder = captor.getValue().getSort().getOrderFor("documentDate");
|
||||
assertThat(dateOrder).isNotNull();
|
||||
assertThat(dateOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(dateOrder.getNullHandling()).isEqualTo(Sort.NullHandling.NULLS_LAST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void searchDocuments_UPDATED_AT_sort_resolves_to_updatedAt_field() {
|
||||
ArgumentCaptor<Pageable> captor = ArgumentCaptor.forClass(Pageable.class);
|
||||
|
||||
Reference in New Issue
Block a user