mirror of
https://github.com/qdrant/qdrant.git
synced 2026-08-04 09:01:00 -05:00
fix(api): reject empty multi-vector in flattened vectors_count path (#9308)
`validate_multi_vector_len(N, &[])` with N > 0 previously returned Ok: it passes the `vectors_count != 0` check, an empty `flatten_dense_vector` clears the size check, and `0.is_multiple_of(N)` is true, so it falls into the Ok branch. The unvalidated value then reaches `convert_to_plain_multi_vector`, where `dim = data.len() / vectors_count = 0`, the divisibility check `dim * vectors_count != data.len()` (0 == 0) passes, and `data.into_iter().chunks(0)` panics (itertools asserts the chunk size is non-zero). This is reachable from a single malformed gRPC Upsert via the deprecated `vectors_count` field. Add an `is_empty()` guard to `validate_multi_vector_len`, mirroring the sibling `validate_multi_vector_by_length`, so empty flattened data is rejected with a clear validation error before any conversion. Also add a defensive `vectors_count == 0 || data.is_empty()` guard at the top of `convert_to_plain_multi_vector`, which aligns it with the already-guarded `MultiDenseVectorInternal::try_from_flatten` and closes the same panic on the internal node-to-node `sync` path (where validation is log-only and `SyncPoints.points` is not validated). This completes the empty-vector hardening started in #9070, which covered the REST side only and did not touch the gRPC flattened `vectors_count` form. Includes a regression test covering both the rejected (empty) and accepted (consistent multivector) cases.
This commit is contained in:
@@ -15,6 +15,12 @@ fn convert_to_plain_multi_vector(
|
||||
data: Vec<f32>,
|
||||
vectors_count: usize,
|
||||
) -> Result<MultiDenseVector, OperationError> {
|
||||
if vectors_count == 0 || data.is_empty() {
|
||||
return Err(OperationError::validation_error(format!(
|
||||
"Empty multi-vector data with vectors count: {vectors_count}"
|
||||
)));
|
||||
}
|
||||
|
||||
let dim = data.len() / vectors_count;
|
||||
if dim * vectors_count != data.len() {
|
||||
return Err(OperationError::validation_error(format!(
|
||||
|
||||
@@ -302,6 +302,14 @@ pub fn validate_multi_vector_len(
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
if flatten_dense_vector.is_empty() {
|
||||
let mut errors = ValidationErrors::default();
|
||||
let mut err = ValidationError::new("empty_multi_vector");
|
||||
err.add_param(Cow::from("message"), &"multi vector must not be empty");
|
||||
errors.add("data", err);
|
||||
return Err(errors);
|
||||
}
|
||||
|
||||
let dense_vector_len = flatten_dense_vector.len();
|
||||
if dense_vector_len >= MAX_MULTIVECTOR_FLATTENED_LEN {
|
||||
let mut errors = ValidationErrors::default();
|
||||
@@ -328,6 +336,17 @@ pub fn validate_multi_vector_len(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_validate_multi_vector_len_rejects_empty_data() {
|
||||
// Regression: empty flattened data with a positive vectors_count must be
|
||||
// rejected. Previously this returned Ok (0.is_multiple_of(N) == true), and
|
||||
// the value then reached convert_to_plain_multi_vector, which builds
|
||||
// chunks(dim) with dim == 0 and panics on the gRPC upsert path.
|
||||
assert!(validate_multi_vector_len(2, &[]).is_err());
|
||||
// A non-empty, consistent multivector still validates.
|
||||
assert!(validate_multi_vector_len(2, &[1.0, 2.0, 3.0, 4.0]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_range_generic() {
|
||||
assert!(validate_range_generic(u64::MIN, None, None).is_ok());
|
||||
|
||||
Reference in New Issue
Block a user