Skip to content

SearchIndex.query() fails on a client configured for RESP3 #825

Description

@vishal-bala

Summary

Every SearchIndex.query() and AsyncSearchIndex.query() on a client created with protocol=3 raises AttributeError, for standalone and cluster clients alike. redis-py parses an FT.SEARCH reply into a dict on RESP3, and process_results reads .docs and .total from it as if it were a Result. clear(), and delete(drop=True) through it, fail the same way because they run a CountQuery.

Clients that RedisVL creates are unaffected, because RedisConnectionFactory sets protocol=2 unless the caller asks otherwise. A plain redis.Redis() or RedisCluster.from_url() with the default protocol is also unaffected, on redis-py 6.3 through 8.1. The failure needs a client passed in with protocol=3, or a URL with ?protocol=3.

Reproduction

Measured on main at 506b961b against Redis 8.4, with redis-py 6.3.0 and 8.1.0:

import redis
from redisvl.index import SearchIndex
from redisvl.query import CountQuery, FilterQuery
from redisvl.query.filter import FilterExpression, Tag

index = SearchIndex.from_dict(
    {"index": {"name": "r3", "prefix": "r3"}, "fields": [{"name": "t", "type": "tag"}]},
    redis_client=redis.Redis(protocol=3),
)
index.create(overwrite=True, drop=True)
index.load([{"t": "a"}])

index.query(FilterQuery(Tag("t") == "a"))
# AttributeError: 'dict' object has no attribute 'docs'   (index.py:544)

index.query(CountQuery(FilterExpression("*")))
# AttributeError: 'dict' object has no attribute 'total'  (index.py:483)
Client TextQuery / FilterQuery / VectorQuery CountQuery, clear()
redis.Redis(), default protocol works works
redis.Redis(protocol=3) fails fails
RedisCluster.from_url(url), default protocol works works
RedisCluster(..., protocol=3) fails fails
SearchIndex(..., redis_url=url) works works

The async clients (redis.asyncio.Redis and redis.asyncio.cluster.RedisCluster) behave the same way with AsyncSearchIndex: they work on the default protocol and fail with the same two errors on protocol=3.

On a cluster client, create() and exists() also fail on RESP3 before any query runs, because of the raw FT.INFO reply; #795 fixes that part.

Cause

_parse_batch_search_result (redisvl/index/index.py:100) already handles this for pipelined searches: when _parse_results returns a dict, it converts it with redis-py's _parse_search_resp3 where that exists, and by hand on redis-py 6.x. The single-query path through _query and cluster_search hands the dict straight to process_results.

Acceptance

  • query() returns the same results on a protocol=3 client as on protocol=2, for at least FilterQuery, VectorQuery and CountQuery, sync and async, standalone and cluster.
  • clear() and delete(drop=True) work on a protocol=3 client.
  • A regression test that fails before the fix. The cluster case needs a hermetic unit test, since cluster tests don't run on pull requests.

Activity

  1. added theissue type on Nov 5, 2026
  2. hyeonsang010716 commented on Nov 6, 2026

    @hyeonsang010716
    Contributor

    I'm taking this one and will open a PR shortly. It converts the reply inside search(), so query(), paginate(), clear() and anything reading search().total work on RESP3, standalone and cluster, sync and async.

    While reproducing it on 930c14e with redis-py 6.4.0, 7.4.1 and 8.1.0, I saw that AggregateHybridQuery ('dict' object has no attribute 'rows') and HybridQuery (... 'results') fail the same way on a protocol=3 client. Their replies have different shapes, so I've left them out of this PR; happy to follow up on them separately.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions