Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions qdrant_client/conversions/conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def convert_collection_info(cls, model: grpc.CollectionInfo) -> rest.CollectionI
payload_schema=cls.convert_payload_schema(model.payload_schema),
segments_count=model.segments_count,
status=cls.convert_collection_status(model.status),
points_count=model.points_count,
points_count=model.points_count if model.HasField("points_count") else None,
indexed_vectors_count=(
model.indexed_vectors_count if model.HasField("indexed_vectors_count") else None
),
Expand Down Expand Up @@ -498,7 +498,7 @@ def convert_collection_status(cls, model: grpc.CollectionStatus) -> rest.Collect
@classmethod
def convert_update_result(cls, model: grpc.UpdateResult) -> rest.UpdateResult:
return rest.UpdateResult(
operation_id=model.operation_id,
operation_id=model.operation_id if model.HasField("operation_id") else None,
status=cls.convert_update_status(model.status),
)

Expand Down
20 changes: 19 additions & 1 deletion tests/conversions/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,15 @@
update_queue=grpc.UpdateQueueInfo(length=42),
)

# `points_count` is `optional` in the proto: the server omits it when the count is
# not available, which must not be reported as an empty collection.
collection_info_no_points_count = grpc.CollectionInfo(
status=collection_status,
optimizer_status=optimizer_status_error,
segments_count=6,
config=collection_config,
)

collection_info_red = grpc.CollectionInfo(
status=collection_status_error,
optimizer_status=optimizer_status_error,
Expand Down Expand Up @@ -1088,6 +1097,9 @@

update_result_completed = grpc.UpdateResult(operation_id=201, status=update_status_completed)
update_result_wait_timeout = grpc.UpdateResult(operation_id=201, status=update_status_wait_timeout)
# `operation_id` is `optional` in the proto: the server omits it, e.g. when a
# delete-by-filter matches nothing or when an update is clock-rejected.
update_result_no_operation_id = grpc.UpdateResult(status=update_status_completed)

delete_alias = grpc.DeleteAlias(alias_name="col3")

Expand Down Expand Up @@ -1882,6 +1894,7 @@
"Filter": [filter_nested, filter_],
"CollectionInfo": [
collection_info,
collection_info_no_points_count,
collection_info_ok,
collection_info_red,
collection_info_grey,
Expand Down Expand Up @@ -2116,7 +2129,12 @@
"SearchMatrixOffsets": [search_matrix_offsets],
"StrictModeConfig": [strict_mode_config, strict_mode_config_empty],
"UpdateQueueInfo": [update_queue_info, update_queue_info_deferred],
"UpdateResult": [update_result, update_result_completed, update_result_wait_timeout],
"UpdateResult": [
update_result,
update_result_completed,
update_result_wait_timeout,
update_result_no_operation_id,
],
"UpdateMode": [update_mode_upsert, update_mode_insert_only, update_mode_update_only],
"ReplicaState": [
replica_state_active,
Expand Down
55 changes: 55 additions & 0 deletions tests/conversions/test_validate_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,58 @@ def round_trip(prefix):
grpc_params, recovered = round_trip(False)
assert not grpc_params.HasField("prefix")
assert recovered.prefix is None


def test_convert_update_result_operation_id_presence():
from qdrant_client import grpc
from qdrant_client.conversions.conversion import GrpcToRest, RestToGrpc

# `optional uint64 operation_id` carries explicit presence: the server leaves it
# unset for updates that were not assigned a sequence number
absent = grpc.UpdateResult(status=grpc.UpdateStatus.Completed)
assert not absent.HasField("operation_id")
assert GrpcToRest.convert_update_result(absent).operation_id is None
assert not RestToGrpc.convert_update_result(GrpcToRest.convert_update_result(absent)).HasField(
"operation_id"
)

# a real operation id of 0 must stay distinguishable from "no operation id"
zero = grpc.UpdateResult(operation_id=0, status=grpc.UpdateStatus.Completed)
assert GrpcToRest.convert_update_result(zero).operation_id == 0
round_tripped = RestToGrpc.convert_update_result(GrpcToRest.convert_update_result(zero))
assert round_tripped.HasField("operation_id")
assert round_tripped.operation_id == 0


def test_convert_collection_info_points_count_presence():
from qdrant_client import grpc
from qdrant_client.conversions.conversion import GrpcToRest, RestToGrpc

config = grpc.CollectionConfig(
params=grpc.CollectionParams(shard_number=1),
hnsw_config=grpc.HnswConfigDiff(m=16, ef_construct=100, full_scan_threshold=10000),
optimizer_config=grpc.OptimizersConfigDiff(default_segment_number=2),
wal_config=grpc.WalConfigDiff(wal_capacity_mb=32, wal_segments_ahead=0),
)

# `optional uint64 points_count` carries explicit presence: an absent count means
# "not available", which is distinct from a collection that holds zero points.
absent = grpc.CollectionInfo(
status=grpc.CollectionStatus.Green,
optimizer_status=grpc.OptimizerStatus(ok=True),
segments_count=1,
config=config,
)
assert not absent.HasField("points_count")
assert GrpcToRest.convert_collection_info(absent).points_count is None
assert not RestToGrpc.convert_collection_info(
GrpcToRest.convert_collection_info(absent)
).HasField("points_count")

zero = grpc.CollectionInfo()
zero.CopyFrom(absent)
zero.points_count = 0
assert GrpcToRest.convert_collection_info(zero).points_count == 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
round_tripped = RestToGrpc.convert_collection_info(GrpcToRest.convert_collection_info(zero))
assert round_tripped.HasField("points_count")
assert round_tripped.points_count == 0