mirror of
https://github.com/qdrant/qdrant.git
synced 2026-07-23 11:11:00 -05:00
317 lines
9.9 KiB
Rust
317 lines
9.9 KiB
Rust
#![expect(clippy::wildcard_enum_match_arm, reason = "test code")]
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::num::NonZeroU32;
|
|
use std::path::Path;
|
|
|
|
use api::rest::SearchRequestInternal;
|
|
use collection::collection::Collection;
|
|
use collection::config::{CollectionConfigInternal, CollectionParams, WalConfig};
|
|
use collection::operations::CollectionUpdateOperations;
|
|
use collection::operations::point_ops::{
|
|
PointInsertOperationsInternal, PointOperations, PointStructPersisted, VectorStructPersisted,
|
|
WriteOrdering,
|
|
};
|
|
use collection::operations::shard_selector_internal::ShardSelectorInternal;
|
|
use collection::operations::types::{
|
|
CollectionError, PointRequestInternal, RecommendRequestInternal, VectorsConfig,
|
|
};
|
|
use collection::operations::vector_params_builder::VectorParamsBuilder;
|
|
use collection::recommendations::recommend_by;
|
|
use common::counter::hardware_accumulator::HwMeasurementAcc;
|
|
use segment::data_types::named_vectors::NamedVectors;
|
|
use segment::data_types::vectors::{NamedVector, VectorStructInternal};
|
|
use segment::types::{Distance, VectorName, WithPayloadInterface, WithVector};
|
|
use tempfile::Builder;
|
|
|
|
use crate::common::{N_SHARDS, TEST_OPTIMIZERS_CONFIG, new_local_collection};
|
|
|
|
const VECTOR1_NAME: &VectorName = "vec1";
|
|
const VECTOR2_NAME: &VectorName = "vec2";
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn test_multi_vec() {
|
|
test_multi_vec_with_shards(1).await;
|
|
test_multi_vec_with_shards(N_SHARDS).await;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub async fn multi_vec_collection_fixture(collection_path: &Path, shard_number: u32) -> Collection {
|
|
let wal_config = WalConfig {
|
|
wal_capacity_mb: 1,
|
|
wal_segments_ahead: 0,
|
|
wal_retain_closed: 1,
|
|
};
|
|
|
|
let vector_params1 = VectorParamsBuilder::new(4, Distance::Dot).build();
|
|
let vector_params2 = VectorParamsBuilder::new(4, Distance::Dot).build();
|
|
|
|
let mut vectors_config = BTreeMap::new();
|
|
|
|
vectors_config.insert(VECTOR1_NAME.to_owned(), vector_params1);
|
|
vectors_config.insert(VECTOR2_NAME.to_owned(), vector_params2);
|
|
|
|
let collection_params = CollectionParams {
|
|
vectors: VectorsConfig::Multi(vectors_config),
|
|
shard_number: NonZeroU32::new(shard_number).expect("Shard number can not be zero"),
|
|
..CollectionParams::empty()
|
|
};
|
|
|
|
let collection_config = CollectionConfigInternal {
|
|
params: collection_params,
|
|
optimizer_config: TEST_OPTIMIZERS_CONFIG.clone(),
|
|
wal_config,
|
|
hnsw_config: Default::default(),
|
|
quantization_config: Default::default(),
|
|
strict_mode_config: Default::default(),
|
|
uuid: None,
|
|
metadata: None,
|
|
};
|
|
|
|
let snapshot_path = collection_path.join("snapshots");
|
|
|
|
// Default to a collection with all the shards local
|
|
new_local_collection(
|
|
"test".to_string(),
|
|
collection_path,
|
|
&snapshot_path,
|
|
&collection_config,
|
|
)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
async fn test_multi_vec_with_shards(shard_number: u32) {
|
|
let collection_dir = Builder::new()
|
|
.prefix("test_multi_vec_with_shards")
|
|
.tempdir()
|
|
.unwrap();
|
|
|
|
let collection = multi_vec_collection_fixture(collection_dir.path(), shard_number).await;
|
|
|
|
// Upload 1000 random vectors to the collection
|
|
let mut points = Vec::new();
|
|
for i in 0..1000 {
|
|
let mut vectors = NamedVectors::default();
|
|
vectors.insert(
|
|
VECTOR1_NAME.to_owned(),
|
|
vec![i as f32, 0.0, 0.0, 0.0].into(),
|
|
);
|
|
vectors.insert(
|
|
VECTOR2_NAME.to_owned(),
|
|
vec![0.0, i as f32, 0.0, 0.0].into(),
|
|
);
|
|
|
|
points.push(PointStructPersisted {
|
|
id: i.into(),
|
|
vector: VectorStructPersisted::from(VectorStructInternal::from(vectors)),
|
|
payload: Some(serde_json::from_str(r#"{"number": "John Doe"}"#).unwrap()),
|
|
});
|
|
}
|
|
let insert_points = CollectionUpdateOperations::PointOperation(PointOperations::UpsertPoints(
|
|
PointInsertOperationsInternal::PointsList(points),
|
|
));
|
|
let hw_counter = HwMeasurementAcc::new();
|
|
collection
|
|
.update_from_client_simple(
|
|
insert_points,
|
|
true,
|
|
None,
|
|
WriteOrdering::default(),
|
|
hw_counter,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let query_vector = vec![6.0, 0.0, 0.0, 0.0];
|
|
|
|
let full_search_request = SearchRequestInternal {
|
|
vector: NamedVector {
|
|
name: VECTOR1_NAME.to_owned(),
|
|
vector: query_vector,
|
|
}
|
|
.into(),
|
|
filter: None,
|
|
limit: 10,
|
|
offset: None,
|
|
with_payload: Some(WithPayloadInterface::Bool(true)),
|
|
with_vector: Some(true.into()),
|
|
params: None,
|
|
score_threshold: None,
|
|
};
|
|
|
|
let hw_acc = HwMeasurementAcc::new();
|
|
let result = collection
|
|
.search(
|
|
full_search_request.into(),
|
|
None,
|
|
&ShardSelectorInternal::All,
|
|
None,
|
|
hw_acc,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
for hit in result {
|
|
match hit.vector.unwrap() {
|
|
VectorStructInternal::Single(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::MultiDense(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::Named(vectors) => {
|
|
assert!(vectors.contains_key(VECTOR1_NAME));
|
|
assert!(vectors.contains_key(VECTOR2_NAME));
|
|
}
|
|
}
|
|
}
|
|
|
|
let query_vector = vec![0.0, 2.0, 0.0, 0.0];
|
|
|
|
let failed_search_request = SearchRequestInternal {
|
|
vector: query_vector.clone().into(),
|
|
filter: None,
|
|
limit: 10,
|
|
offset: None,
|
|
with_payload: Some(WithPayloadInterface::Bool(true)),
|
|
with_vector: Some(true.into()),
|
|
params: None,
|
|
score_threshold: None,
|
|
};
|
|
|
|
let hw_acc = HwMeasurementAcc::new();
|
|
let result = collection
|
|
.search(
|
|
failed_search_request.into(),
|
|
None,
|
|
&ShardSelectorInternal::All,
|
|
None,
|
|
hw_acc,
|
|
)
|
|
.await;
|
|
|
|
assert!(
|
|
matches!(result, Err(CollectionError::BadInput { .. })),
|
|
"{result:?}"
|
|
);
|
|
|
|
let full_search_request = SearchRequestInternal {
|
|
vector: NamedVector {
|
|
name: VECTOR2_NAME.to_owned(),
|
|
vector: query_vector,
|
|
}
|
|
.into(),
|
|
filter: None,
|
|
limit: 10,
|
|
offset: None,
|
|
with_payload: Some(WithPayloadInterface::Bool(true)),
|
|
with_vector: Some(true.into()),
|
|
params: None,
|
|
score_threshold: None,
|
|
};
|
|
|
|
let hw_acc = HwMeasurementAcc::new();
|
|
let result = collection
|
|
.search(
|
|
full_search_request.into(),
|
|
None,
|
|
&ShardSelectorInternal::All,
|
|
None,
|
|
hw_acc,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
for hit in result {
|
|
match hit.vector.unwrap() {
|
|
VectorStructInternal::Single(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::MultiDense(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::Named(vectors) => {
|
|
assert!(vectors.contains_key(VECTOR1_NAME));
|
|
assert!(vectors.contains_key(VECTOR2_NAME));
|
|
}
|
|
}
|
|
}
|
|
|
|
let retrieve = collection
|
|
.retrieve(
|
|
PointRequestInternal {
|
|
ids: vec![6.into()],
|
|
with_payload: Some(WithPayloadInterface::Bool(false)),
|
|
with_vector: WithVector::Selector(vec![VECTOR1_NAME.to_owned()]),
|
|
},
|
|
None,
|
|
&ShardSelectorInternal::All,
|
|
None,
|
|
HwMeasurementAcc::new(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(retrieve.len(), 1);
|
|
match retrieve[0].vector.as_ref().unwrap() {
|
|
VectorStructInternal::Single(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::MultiDense(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::Named(vectors) => {
|
|
assert!(vectors.contains_key(VECTOR1_NAME));
|
|
assert!(!vectors.contains_key(VECTOR2_NAME));
|
|
}
|
|
}
|
|
|
|
let hw_acc = HwMeasurementAcc::new();
|
|
let recommend_result = recommend_by(
|
|
RecommendRequestInternal {
|
|
positive: vec![6.into()],
|
|
with_payload: Some(WithPayloadInterface::Bool(false)),
|
|
with_vector: Some(WithVector::Selector(vec![VECTOR2_NAME.to_owned()])),
|
|
limit: 10,
|
|
..Default::default()
|
|
},
|
|
&collection,
|
|
|_name| async { unreachable!("should not be called in this test") },
|
|
None,
|
|
ShardSelectorInternal::All,
|
|
None,
|
|
hw_acc,
|
|
)
|
|
.await;
|
|
|
|
match recommend_result {
|
|
Ok(_) => panic!("Error expected"),
|
|
Err(err) => match err {
|
|
CollectionError::BadRequest { .. } => {}
|
|
CollectionError::BadInput { .. } => {}
|
|
error => panic!("Unexpected error {error}"),
|
|
},
|
|
}
|
|
|
|
let hw_acc = HwMeasurementAcc::new();
|
|
let recommend_result = recommend_by(
|
|
RecommendRequestInternal {
|
|
positive: vec![6.into()],
|
|
with_payload: Some(WithPayloadInterface::Bool(false)),
|
|
with_vector: Some(WithVector::Selector(vec![VECTOR2_NAME.to_owned()])),
|
|
limit: 10,
|
|
using: Some(VECTOR1_NAME.to_owned().into()),
|
|
..Default::default()
|
|
},
|
|
&collection,
|
|
|_name| async { unreachable!("should not be called in this test") },
|
|
None,
|
|
ShardSelectorInternal::All,
|
|
None,
|
|
hw_acc,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(recommend_result.len(), 10);
|
|
for hit in recommend_result {
|
|
match hit.vector.as_ref().unwrap() {
|
|
VectorStructInternal::Single(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::MultiDense(_) => panic!("expected multi vector"),
|
|
VectorStructInternal::Named(vectors) => {
|
|
assert!(!vectors.contains_key(VECTOR1_NAME));
|
|
assert!(vectors.contains_key(VECTOR2_NAME));
|
|
}
|
|
}
|
|
}
|
|
}
|