Description
A filtered, sorted, and paged query against a Corax static index can return incorrect documents when textual sort values collide on Corax's compact prefix key. The query results then depend on index ingestion order rather than the complete sort values.
The ISO strings reflect a real use case: RavenDB.Client.NodaTime serializes NodaTime.LocalDate in yyyy-MM-dd form. These values are then treated as a string and therefore use textual Sequence sorting which fails for these types of values.
DateTime, native DateOnly, Instant, and LocalDateTime use RavenDB's temporal indexing paths and sort correctly.
Reproduced with:
- RavenDB 6.2.17, build 62092
- Current
release/v6.2: 6.2.20-custom-62
Full reproduction:
https://github.com/wagich/RavenDbCoraxSortedPagingRepro
Reduced reproduction
using Raven.Client.Documents.Indexes;
using Raven.TestDriver;
using Xunit;
public class CoraxSortedPagingTest : RavenTestDriver
{
private sealed class Item
{
public string Id { get; set; } = default!;
public string Category { get; set; } = default!;
public string Date { get; set; } = default!;
public string Name { get; set; } = default!;
}
private sealed class Items_ByCategory : AbstractIndexCreationTask<Item>
{
public Items_ByCategory()
{
Map = items => from item in items
select new { item.Category, item.Date };
}
}
[Fact]
public async Task Should_page_after_sorting_complete_terms()
{
using var store = GetDocumentStore();
await new Items_ByCategory().ExecuteAsync(store);
using (var session = store.OpenAsyncSession())
{
await session.StoreAsync(new Item { Id = "items/A", Name = "A", Category = "News", Date = "2026-09-01" });
await session.StoreAsync(new Item { Id = "items/B", Name = "B", Category = "News", Date = "2026-08-01" });
await session.StoreAsync(new Item { Id = "items/C", Name = "C", Category = "News", Date = "2026-07-01" });
await session.StoreAsync(new Item { Id = "items/D", Name = "D", Category = "News", Date = "2026-06-01" });
await session.SaveChangesAsync();
}
WaitForIndexing(store);
using var querySession = store.OpenAsyncSession();
var page = await querySession.Query<Item, Items_ByCategory>()
.Where(item => item.Category == "News")
.OrderByDescending(item => item.Date)
.Take(2)
.ToListAsync();
Assert.Equal(new[] { "A", "B" }, page.Select(item => item.Name));
}
}
Expected: A,B
Actual: C,D
Without Take(2), the query correctly returns A,B,C,D.
Root cause
In SortingMatch<TInner>.EntryComparerByTerm.SortByTerms, Corax:
- Builds an approximate key from at most six bytes of each compact encoded term.
- Sorts those approximate keys.
- Truncates the results to
_take.
- Resolves equal prefixes using the complete terms.
Consequently, candidates tied on the approximate key can be discarded before the complete-term comparison determines their correct order.
Moving MaybeBreakTies(...) before the _take truncation made all reproduction tests pass against 6.2.20-custom-62. A narrower correction could resolve the tie group crossing the cutoff before truncating.
Description
A filtered, sorted, and paged query against a Corax static index can return incorrect documents when textual sort values collide on Corax's compact prefix key. The query results then depend on index ingestion order rather than the complete sort values.
The ISO strings reflect a real use case:
RavenDB.Client.NodaTimeserializesNodaTime.LocalDateinyyyy-MM-ddform. These values are then treated as a string and therefore use textualSequencesorting which fails for these types of values.DateTime, nativeDateOnly,Instant, andLocalDateTimeuse RavenDB's temporal indexing paths and sort correctly.Reproduced with:
release/v6.2: 6.2.20-custom-62Full reproduction:
https://github.com/wagich/RavenDbCoraxSortedPagingRepro
Reduced reproduction
Expected:
A,BActual:
C,DWithout
Take(2), the query correctly returnsA,B,C,D.Root cause
In
SortingMatch<TInner>.EntryComparerByTerm.SortByTerms, Corax:_take.Consequently, candidates tied on the approximate key can be discarded before the complete-term comparison determines their correct order.
Moving
MaybeBreakTies(...)before the_taketruncation made all reproduction tests pass against6.2.20-custom-62. A narrower correction could resolve the tie group crossing the cutoff before truncating.