use std::str::FromStr; use std::sync::Arc; use common::cpu::CpuBudget; use segment::json_path::JsonPath; use segment::types::{ Condition, FieldCondition, Filter, GeoPoint, GeoRadius, PayloadFieldSchema, PayloadSchemaType, Range, }; use tempfile::Builder; use tokio::runtime::Handle; use tokio::sync::RwLock; use crate::collection::payload_index_schema::PayloadIndexSchema; use crate::operations::{CollectionUpdateOperations, CreateIndex, FieldIndexOperations}; use crate::save_on_disk::SaveOnDisk; use crate::shards::local_shard::LocalShard; use crate::shards::shard_trait::ShardOperation; use crate::tests::fixtures::{create_collection_config, upsert_operation}; #[tokio::test(flavor = "multi_thread")] async fn test_payload_missing_index_check() { let collection_dir = Builder::new().prefix("test_collection").tempdir().unwrap(); let config = create_collection_config(); let collection_name = "test".to_string(); let current_runtime: Handle = Handle::current(); let payload_index_schema_dir = Builder::new().prefix("qdrant-test").tempdir().unwrap(); let payload_index_schema_file = payload_index_schema_dir.path().join("payload-schema.json"); let payload_index_schema = Arc::new(SaveOnDisk::load_or_init_default(payload_index_schema_file.clone()).unwrap()); let shard = LocalShard::build( 0, collection_name.clone(), collection_dir.path(), Arc::new(RwLock::new(config.clone())), Arc::new(Default::default()), payload_index_schema.clone(), current_runtime.clone(), current_runtime.clone(), CpuBudget::default(), config.optimizer_config.clone(), ) .await .unwrap(); let upsert_ops = upsert_operation(); shard.update(upsert_ops.into(), true).await.unwrap(); let geo_filter = Filter::new_must(Condition::Field(FieldCondition::new_geo_radius( JsonPath::from_str("location").unwrap(), GeoRadius { center: GeoPoint::new(12.0, 34.0).ok().unwrap(), radius: 50.0, }, ))); // No index yet => Filter has unindexed field assert_eq!( shard .payload_index_schema .read() .one_unindexed_key(&geo_filter) .map(|(x, _)| x), Some(JsonPath::from_str("location").unwrap()) ); // Create unnested index create_index( &shard, &payload_index_schema, "location", PayloadSchemaType::Geo, ) .await; // Index created => Filter shouldn't have any unindexed field anymore assert_eq!( shard .payload_index_schema .read() .one_unindexed_key(&geo_filter), None ); // Create nested filter let condition = Condition::new_nested( JsonPath::new("location"), Filter::new_must(Condition::Field(FieldCondition::new_range( JsonPath::new("lat"), Range { gt: Some(12.into()), ..Default::default() }, ))), ); let num_filter = Filter::new_must(condition); // Index only exists for 'location' but not 'location.lat' // so we expect it to be detected as unindexed assert_eq!( shard .payload_index_schema .read() .one_unindexed_key(&num_filter) .map(|(x, _)| x), Some("location.lat".parse().unwrap()) ); // Create index for nested field create_index( &shard, &payload_index_schema, "location.lat", PayloadSchemaType::Float, ) .await; // Nested field also gets detected as indexed and unindexed fields in the query are empty. assert_eq!( shard .payload_index_schema .read() .one_unindexed_key(&num_filter), None, ); // Filters combined also completely indexed! let combined_filter = geo_filter.merge(&num_filter); assert_eq!( shard .payload_index_schema .read() .one_unindexed_key(&combined_filter), None, ); } async fn create_index( shard: &LocalShard, payload_index_schema: &Arc>, name: &str, field_type: PayloadSchemaType, ) { payload_index_schema .write(|schema| { schema.schema.insert( name.parse().unwrap(), PayloadFieldSchema::FieldType(field_type), ); }) .unwrap(); let create_index = CollectionUpdateOperations::FieldIndexOperation( FieldIndexOperations::CreateIndex(CreateIndex { field_name: name.parse().unwrap(), field_schema: Some(PayloadFieldSchema::FieldType(field_type)), }), ); shard.update(create_index.into(), true).await.unwrap(); }