Repository navigation
Description
Summary
FT.SEARCH and FT.AGGREGATE accept WITHCOUNT and WITHOUTCOUNT, but neither Query nor AggregationRequest can emit them. As of master (71bda71) and v1.8.0, the only WITHCOUNT in the library is TOPK.LIST's.
Why it matters
WITHOUTCOUNT turns on the query optimizer that DIALECT 4 enables by default. For a query sorted by a numeric field with a limit, the optimizer walks the field's numeric range tree in sort order and stops once it has enough results, instead of evaluating and sorting every match. The command docs describe it as an alternative to DIALECT 4 for both commands:
You can also use
WITHOUTCOUNTin place ofDIALECT 4when used with eitherFT.SEARCHorFT.AGGREGATE.
https://redis.io/docs/latest/commands/ft.search/ and https://redis.io/docs/latest/commands/ft.aggregate/
WITHCOUNT is the opposite: it keeps an exact total count, which the optimized path does not compute.
Here is the difference on 1M documents with a NUMERIC SORTABLE field (redislabs/client-libs-test:8.10.0, server-side time from INFO commandstats):
@n:[(cursor +inf] sorted by n, MAX 10 |
Cursor at start | Middle | Near end |
|---|---|---|---|
FT.AGGREGATE |
38.6 ms | 19.6 ms | 0.43 ms |
FT.AGGREGATE … WITHOUTCOUNT |
0.15 ms | 0.15 ms | 0.12 ms |
For keyset pagination, that's the difference between a constant-cost page and one that costs the whole remaining result set. Today the only way to get it from NRedisStack is DIALECT 4, which changes other query semantics too.
Argument position
The server parses WITHCOUNT/WITHOUTCOUNT among the request options. In FT.AGGREGATE that means before the first pipeline step: … LOAD 1 @x WITHOUTCOUNT fails with Unknown argument 'WITHOUTCOUNT', while FT.AGGREGATE idx <query> WITHOUTCOUNT … works. The docs describe WITHCOUNT alongside SORTBY, so a builder method that just appends the token wherever it's called would easily produce commands the server rejects.
Proposal
Query: abool? WithCountproperty.trueemitsWITHCOUNT,falseemitsWITHOUTCOUNT, andnull(the default) emits neither. FluentSetWithCount()/SetWithoutCount()set it, like the existingSetWithScores().AggregationRequest: fluentWithCount()/WithoutCount(). The token always goes directly after the query string, whatever the call order, and the later call wins if both are made.
I have a PR with tests ready and will link it here.
Activity
aradalvand commentedon Nov 9, 2026
PR: #592