mirror of
https://github.com/qdrant/qdrant.git
synced 2026-07-23 11:11:00 -05:00
* fix: validate vector dimensions before WAL write for async upserts When upserting points with wait=false (the default), dimension mismatches were silently discarded during background processing. The API returned 200 "acknowledged" but the points were never stored, causing silent data loss with no error feedback to the user. This adds an early dimension validation check in do_upsert_points() that runs before the operation is written to WAL. This ensures that dimension errors are returned to the client regardless of the wait parameter, matching the behavior of wait=true. The validation handles all vector types: - Dense single vectors - Multi-dense vectors - Named vectors (dense, multi-dense, sparse) - Sparse vectors are skipped (no fixed dimension) Closes #9039 Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: move vector dimension validation into dedicated module Extract validate_vector_dimensions and helper functions from update.rs into src/common/validate_vectors.rs for better code organization. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: update shard update test for early dimension validation The test expected a shard-level error message, but now dimension mismatches are caught before reaching the shards. Update the assertion to accept either the early validation error or the shard-level error. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: assert actual dimension error message in shard update test Check for the descriptive error ("Vector dimension error: expected dim: 4, got 3") rather than the generic shard failure wrapper. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <agent@cursor.com> Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.3 KiB
Python
106 lines
3.3 KiB
Python
"""
|
|
Test that vector dimension validation works for both sync (wait=true) and async (wait=false) upserts.
|
|
|
|
Regression test for https://github.com/qdrant/qdrant/issues/9039
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from .helpers.collection_setup import drop_collection
|
|
from .helpers.helpers import request_with_validation
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(collection_name):
|
|
drop_collection(collection_name=collection_name)
|
|
|
|
response = request_with_validation(
|
|
api='/collections/{collection_name}',
|
|
method="PUT",
|
|
path_params={'collection_name': collection_name},
|
|
body={
|
|
"vectors": {
|
|
"size": 4,
|
|
"distance": "Cosine",
|
|
},
|
|
}
|
|
)
|
|
assert response.ok
|
|
|
|
yield
|
|
drop_collection(collection_name=collection_name)
|
|
|
|
|
|
def test_sync_upsert_wrong_dimension_rejected(collection_name):
|
|
"""With wait=true, wrong-dimension vectors should be rejected with 4xx."""
|
|
response = request_with_validation(
|
|
api='/collections/{collection_name}/points',
|
|
method="PUT",
|
|
path_params={'collection_name': collection_name},
|
|
query_params={'wait': 'true'},
|
|
body={
|
|
"points": [
|
|
{"id": 1, "vector": [0.1, 0.2, 0.3]} # 3-dim into 4-dim
|
|
]
|
|
}
|
|
)
|
|
assert not response.ok
|
|
assert response.status_code == 400
|
|
assert "dimension" in response.json()["status"]["error"].lower()
|
|
|
|
|
|
def test_async_upsert_wrong_dimension_rejected(collection_name):
|
|
"""With wait=false (default), wrong-dimension vectors should also be rejected with 4xx."""
|
|
response = request_with_validation(
|
|
api='/collections/{collection_name}/points',
|
|
method="PUT",
|
|
path_params={'collection_name': collection_name},
|
|
query_params={'wait': 'false'},
|
|
body={
|
|
"points": [
|
|
{"id": 2, "vector": [0.1, 0.2, 0.3]} # 3-dim into 4-dim
|
|
]
|
|
}
|
|
)
|
|
assert not response.ok
|
|
assert response.status_code == 400
|
|
assert "dimension" in response.json()["status"]["error"].lower()
|
|
|
|
|
|
def test_async_upsert_correct_dimension_accepted(collection_name):
|
|
"""With wait=false, correct-dimension vectors should still succeed."""
|
|
response = request_with_validation(
|
|
api='/collections/{collection_name}/points',
|
|
method="PUT",
|
|
path_params={'collection_name': collection_name},
|
|
query_params={'wait': 'false'},
|
|
body={
|
|
"points": [
|
|
{"id": 3, "vector": [0.1, 0.2, 0.3, 0.4]} # 4-dim into 4-dim
|
|
]
|
|
}
|
|
)
|
|
assert response.ok
|
|
|
|
|
|
def test_async_batch_upsert_wrong_dimension_rejected(collection_name):
|
|
"""Batch upsert with wait=false should also reject wrong-dimension vectors."""
|
|
response = request_with_validation(
|
|
api='/collections/{collection_name}/points',
|
|
method="PUT",
|
|
path_params={'collection_name': collection_name},
|
|
query_params={'wait': 'false'},
|
|
body={
|
|
"batch": {
|
|
"ids": [10, 11],
|
|
"vectors": [
|
|
[0.1, 0.2, 0.3, 0.4], # correct
|
|
[0.1, 0.2, 0.3], # wrong dimension
|
|
]
|
|
}
|
|
}
|
|
)
|
|
assert not response.ok
|
|
assert response.status_code == 400
|
|
assert "dimension" in response.json()["status"]["error"].lower()
|