Skip to content

Commit 19c1029

Browse files
Michelangelo Partipiloclaude
andcommitted
Add shard cursor support to filtered iterators
Feed shard_cursors from each SearchReply back into the next SearchRequest, enabling per-shard pagination state for filtered iterators. Also updates protobuf definitions to include the shard_cursors field. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 046cdb8 commit 19c1029

44 files changed

Lines changed: 1803 additions & 501 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

weaviate/collections/classes/internal.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,15 @@ class GroupByReturn(Generic[P, R]):
269269

270270
@dataclass
271271
class QueryReturn(Generic[P, R]):
272-
"""The return type of a query within the `.query` namespace of a collection."""
272+
"""The return type of a query within the `.query` namespace of a collection.
273+
274+
Attributes:
275+
objects: List of objects returned from the query.
276+
shard_cursors: Optional dictionary mapping shard IDs to cursor positions for paginated queries.
277+
"""
273278

274279
objects: List[Object[P, R]]
280+
shard_cursors: Optional[Dict[str, str]] = None
275281

276282

277283
_GQLEntryReturnType: TypeAlias = Dict[str, List[Dict[str, Any]]]

weaviate/collections/grpc/query.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def get(
121121
return_references: Optional[REFERENCES] = None,
122122
generative: Optional[_Generative] = None,
123123
rerank: Optional[Rerank] = None,
124+
shard_cursors: Optional[Dict[str, str]] = None,
124125
) -> search_get_pb2.SearchRequest:
125126
if self._validate_arguments:
126127
_validate_input(_ValidateArgument([_Sorting, None], "sort", sort))
@@ -144,6 +145,7 @@ def get(
144145
generative=generative,
145146
rerank=rerank,
146147
sort_by=sort_by,
148+
shard_cursors=shard_cursors,
147149
)
148150

149151
def hybrid(
@@ -416,6 +418,7 @@ def __create_request(
416418
near_imu: Optional[base_search_pb2.NearIMUSearch] = None,
417419
near_thermal: Optional[base_search_pb2.NearThermalSearch] = None,
418420
near_video: Optional[base_search_pb2.NearVideoSearch] = None,
421+
shard_cursors: Optional[Dict[str, str]] = None,
419422
) -> search_get_pb2.SearchRequest:
420423
if self._validate_arguments:
421424
_validate_input(
@@ -507,6 +510,7 @@ def __create_request(
507510
near_imu=near_imu,
508511
near_thermal=near_thermal,
509512
near_video=near_video,
513+
shard_cursors=shard_cursors if shard_cursors else {},
510514
)
511515

512516
def _metadata_to_grpc(self, metadata: _MetadataQuery) -> search_get_pb2.MetadataRequest:

weaviate/collections/iterator.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Any,
44
AsyncIterable,
55
AsyncIterator,
6+
Dict,
67
Generic,
78
Iterable,
89
Iterator,
@@ -62,16 +63,24 @@ def __init__(
6263
self.__iter_object_cache: List[Object[TProperties, TReferences]] = []
6364
self.__iter_object_last_uuid: Optional[UUIDorStr] = _parse_after(self.__inputs.after)
6465
self.__iter_cache_size = cache_size or ITERATOR_CACHE_SIZE
66+
self.__iter_shard_cursors: Optional[Dict[str, str]] = None
6567

6668
def __iter__(
6769
self,
6870
) -> Iterator[Object[TProperties, TReferences]]:
6971
self.__iter_object_cache = []
7072
self.__iter_object_last_uuid = _parse_after(self.__inputs.after)
73+
self.__iter_shard_cursors = None
7174
return self
7275

7376
def __next__(self) -> Object[TProperties, TReferences]:
7477
if len(self.__iter_object_cache) == 0:
78+
# Shard cursor pagination:
79+
# - First call uses shard_cursors=None; subsequent calls include the cursors
80+
# returned by the server in the previous SearchReply.
81+
# - The server returns updated shard_cursors in each response, which are fed
82+
# back into the next request so each shard resumes from the right position.
83+
# - Iteration ends when the server returns an empty result set.
7584
res = self.__query.fetch_objects(
7685
limit=self.__iter_cache_size,
7786
after=self.__iter_object_last_uuid,
@@ -80,8 +89,10 @@ def __next__(self) -> Object[TProperties, TReferences]:
8089
return_properties=self.__inputs.return_properties,
8190
return_references=self.__inputs.return_references,
8291
filters=self.__inputs.filters,
92+
shard_cursors=self.__iter_shard_cursors,
8393
)
8494
self.__iter_object_cache = res.objects # type: ignore
95+
self.__iter_shard_cursors = res.shard_cursors
8596
if len(self.__iter_object_cache) == 0:
8697
raise StopIteration
8798

@@ -109,18 +120,26 @@ def __init__(
109120
self.__iter_object_cache: List[Object[TProperties, TReferences]] = []
110121
self.__iter_object_last_uuid: UUIDorStr = _parse_after(self.__inputs.after)
111122
self.__iter_cache_size = cache_size or ITERATOR_CACHE_SIZE
123+
self.__iter_shard_cursors: Optional[Dict[str, str]] = None
112124

113125
def __aiter__(
114126
self,
115127
) -> AsyncIterator[Object[TProperties, TReferences]]:
116128
self.__iter_object_cache = []
117129
self.__iter_object_last_uuid = _parse_after(self.__inputs.after)
130+
self.__iter_shard_cursors = None
118131
return self
119132

120133
async def __anext__(
121134
self,
122135
) -> Object[TProperties, TReferences]:
123136
if len(self.__iter_object_cache) == 0:
137+
# Shard cursor pagination:
138+
# - First call uses shard_cursors=None; subsequent calls include the cursors
139+
# returned by the server in the previous SearchReply.
140+
# - The server returns updated shard_cursors in each response, which are fed
141+
# back into the next request so each shard resumes from the right position.
142+
# - Iteration ends when the server returns an empty result set.
124143
res = await self.__query.fetch_objects(
125144
limit=self.__iter_cache_size,
126145
after=self.__iter_object_last_uuid,
@@ -129,8 +148,10 @@ async def __anext__(
129148
return_properties=self.__inputs.return_properties,
130149
return_references=self.__inputs.return_references,
131150
filters=self.__inputs.filters,
151+
shard_cursors=self.__iter_shard_cursors,
132152
)
133153
self.__iter_object_cache = res.objects # type: ignore
154+
self.__iter_shard_cursors = res.shard_cursors
134155
if len(self.__iter_object_cache) == 0:
135156
raise StopAsyncIteration
136157

weaviate/collections/queries/base_executor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,14 @@ def _result_to_query_return(
457457
res: search_get_pb2.SearchReply,
458458
options: _QueryOptions,
459459
) -> QueryReturn[WeaviateProperties, CrossReferences]:
460+
# Extract shard_cursors from protobuf map and convert to Python dict
461+
shard_cursors = dict(res.shard_cursors) if res.shard_cursors else None
460462
return QueryReturn(
461463
objects=[
462464
self.__result_to_query_object(obj.properties, obj.metadata, options)
463465
for obj in res.results
464-
]
466+
],
467+
shard_cursors=shard_cursors,
465468
)
466469

467470
def _result_to_generative_query_return(

weaviate/collections/queries/fetch_objects/query/executor.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Generic, Literal, Optional, Type, Union, cast, overload
1+
from typing import Any, Dict, Generic, Literal, Optional, Type, Union, cast, overload
22

33
from weaviate.collections.classes.filters import _Filters
44
from weaviate.collections.classes.grpc import METADATA, PROPERTIES, REFERENCES, Sorting
@@ -39,6 +39,7 @@ def fetch_objects(
3939
return_metadata: Optional[METADATA] = None,
4040
return_properties: Union[PROPERTIES, bool, None] = None,
4141
return_references: Literal[None] = None,
42+
shard_cursors: Optional[Dict[str, str]] = None,
4243
) -> executor.Result[QueryReturn[Properties, References]]: ...
4344

4445
@overload
@@ -54,6 +55,7 @@ def fetch_objects(
5455
return_metadata: Optional[METADATA] = None,
5556
return_properties: Union[PROPERTIES, bool, None] = None,
5657
return_references: REFERENCES,
58+
shard_cursors: Optional[Dict[str, str]] = None,
5759
) -> executor.Result[QueryReturn[Properties, CrossReferences]]: ...
5860

5961
@overload
@@ -69,6 +71,7 @@ def fetch_objects(
6971
return_metadata: Optional[METADATA] = None,
7072
return_properties: Union[PROPERTIES, bool, None] = None,
7173
return_references: Type[TReferences],
74+
shard_cursors: Optional[Dict[str, str]] = None,
7275
) -> executor.Result[QueryReturn[Properties, TReferences]]: ...
7376

7477
@overload
@@ -84,6 +87,7 @@ def fetch_objects(
8487
return_metadata: Optional[METADATA] = None,
8588
return_properties: Type[TProperties],
8689
return_references: Literal[None] = None,
90+
shard_cursors: Optional[Dict[str, str]] = None,
8791
) -> executor.Result[QueryReturn[TProperties, References]]: ...
8892

8993
@overload
@@ -99,6 +103,7 @@ def fetch_objects(
99103
return_metadata: Optional[METADATA] = None,
100104
return_properties: Type[TProperties],
101105
return_references: REFERENCES,
106+
shard_cursors: Optional[Dict[str, str]] = None,
102107
) -> executor.Result[QueryReturn[TProperties, CrossReferences]]: ...
103108

104109
@overload
@@ -114,6 +119,7 @@ def fetch_objects(
114119
return_metadata: Optional[METADATA] = None,
115120
return_properties: Type[TProperties],
116121
return_references: Type[TReferences],
122+
shard_cursors: Optional[Dict[str, str]] = None,
117123
) -> executor.Result[QueryReturn[TProperties, TReferences]]: ...
118124

119125
@overload
@@ -129,6 +135,7 @@ def fetch_objects(
129135
return_metadata: Optional[METADATA] = None,
130136
return_properties: Optional[ReturnProperties[TProperties]] = None,
131137
return_references: Optional[ReturnReferences[TReferences]] = None,
138+
shard_cursors: Optional[Dict[str, str]] = None,
132139
) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]: ...
133140

134141
def fetch_objects(
@@ -143,6 +150,7 @@ def fetch_objects(
143150
return_metadata: Optional[METADATA] = None,
144151
return_properties: Optional[ReturnProperties[TProperties]] = None,
145152
return_references: Optional[ReturnReferences[TReferences]] = None,
153+
shard_cursors: Optional[Dict[str, str]] = None,
146154
) -> executor.Result[QueryReturnType[Properties, References, TProperties, TReferences]]:
147155
"""Retrieve the objects in this collection without any search.
148156
@@ -156,6 +164,7 @@ def fetch_objects(
156164
return_metadata: The metadata to return for each object, defaults to `None`.
157165
return_properties: The properties to return for each object.
158166
return_references: The references to return for each object.
167+
shard_cursors: The shard cursors from the previous response for paginated queries.
159168
160169
NOTE:
161170
- If `return_properties` is not provided then all properties are returned except for blob properties.
@@ -195,6 +204,7 @@ def resp(
195204
return_metadata=self._parse_return_metadata(return_metadata, include_vector),
196205
return_properties=self._parse_return_properties(return_properties),
197206
return_references=self._parse_return_references(cast(Any, return_references)),
207+
shard_cursors=shard_cursors,
198208
)
199209
return executor.execute(
200210
response_callback=resp,

0 commit comments

Comments
 (0)