Fix TypeError when sorting heterogeneous facet values / mixed-type point ids in local mode (#1377)

* Fix TypeError sorting heterogeneous facet values and mixed-type point ids in local mode

facet() broke a count tie with the raw facet value and _search_distance_matrix
sorted samples by raw point id; both raise TypeError when the values span
types (e.g. int vs str, or int vs UUID id). Route point-id sorting through the
existing _universal_id helper, and give facet values a dedicated type-safe key
that also keeps equal-count ties deterministic across colliding types (e.g. the
string "" and the int 0). Adds regression tests for both.

* fix: narrow down the fix to search matrix only

---------

Co-authored-by: George Panchuk <george.panchuk@qdrant.tech>
This commit is contained in:
Madan kumar
2026-09-16 00:26:18 +07:00
committed by George Panchuk
co-authored by George Panchuk
parent 69a6ee2299
commit 3ea381504d
2 changed files with 39 additions and 2 deletions
+3 -2
View File
@@ -1737,8 +1737,9 @@ class LocalCollection:
if len(samples) < 2:
return [], []
# sort samples by id
samples = sorted(samples, key=lambda x: x.id)
# sort samples by id; use a type-safe key since a collection may mix
# integer and UUID (str) point ids, which cannot be compared directly
samples = sorted(samples, key=lambda x: self._universal_id(x.id))
# extract the ids
ids = [sample.id for sample in samples]
scores: list[list[ScoredPoint]] = []
@@ -182,3 +182,39 @@ def test_search_pairs_filter(
except AssertionError as e:
print(f"\nAttempt {i} failed with filter {query_filter}")
raise e
def test_search_matrix_with_mixed_id_types():
# Integer and UUID point ids can coexist in one collection. Sampled points are
# ordered by id, so local mode has to order the two id types the way the server
# does instead of comparing them directly.
collection_name = "congruence_search_matrix_mixed_ids"
half = TEST_NUM_POINTS // 2
points = generate_fixtures(half) + generate_fixtures(half, random_ids=True)
local_client = init_local()
init_client(local_client, points, collection_name=collection_name)
http_client = init_remote()
init_client(http_client, points, collection_name=collection_name)
grpc_client = init_remote(prefer_grpc=True)
def search_offsets_mixed_ids(client: QdrantBase) -> models.SearchMatrixOffsetsResponse:
return client.search_matrix_offsets(
collection_name=collection_name,
sample=TEST_NUM_POINTS,
limit=3,
using="text",
)
def search_pairs_mixed_ids(client: QdrantBase) -> models.SearchMatrixPairsResponse:
return client.search_matrix_pairs(
collection_name=collection_name,
sample=TEST_NUM_POINTS,
limit=3,
using="text",
)
compare_all_clients_results(local_client, http_client, grpc_client, search_offsets_mixed_ids)
compare_all_clients_results(local_client, http_client, grpc_client, search_pairs_mixed_ids)