Skip to content

Support WITHCOUNT / WITHOUTCOUNT on FT.SEARCH and FT.AGGREGATE #591

Open

Description

@aradalvand

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 WITHOUTCOUNT in place of DIALECT 4 when used with either FT.SEARCH or FT.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: a bool? WithCount property. true emits WITHCOUNT, false emits WITHOUTCOUNT, and null (the default) emits neither. Fluent SetWithCount() / SetWithoutCount() set it, like the existing SetWithScores().
  • AggregationRequest: fluent WithCount() / 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

  1. aradalvand commented on Nov 9, 2026

    @aradalvand
    Author

    PR: #592

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions