1use lance_core::utils::row_addr_remap::RowAddrRemap;
8use std::any::Any;
9use std::fmt::Debug;
10use std::sync::Arc;
11
12use arrow_array::{ArrayRef, Float32Array, RecordBatch, UInt32Array};
13use arrow_schema::Field;
14use async_trait::async_trait;
15use datafusion::execution::SendableRecordBatchStream;
16use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
17use futures::stream;
18use ivf::storage::IvfModel;
19use lance_core::deepsize::DeepSizeOf;
20use lance_core::{Error, ROW_ID_FIELD, Result};
21use lance_io::traits::Reader;
22use lance_linalg::distance::DistanceType;
23use quantizer::{QuantizationType, Quantizer};
24use std::sync::LazyLock;
25use v3::subindex::SubIndexType;
26
27pub mod bq;
28pub mod distributed;
29pub mod flat;
30pub mod graph;
31pub mod hnsw;
32pub mod ivf;
33pub mod kmeans;
34pub mod pq;
35pub mod quantizer;
36pub mod residual;
37pub mod shared;
38pub mod sq;
39pub mod storage;
40pub mod transform;
41pub mod utils;
42pub mod v3;
43
44use super::pb;
45use crate::metrics::MetricsCollector;
46use crate::{Index, prefilter::PreFilter};
47
48pub const DIST_COL: &str = "_distance";
50pub const DISTANCE_TYPE_KEY: &str = "distance_type";
51pub const INDEX_UUID_COLUMN: &str = "__index_uuid";
52pub const PART_ID_COLUMN: &str = "__ivf_part_id";
53pub const DIST_Q_C_COLUMN: &str = "__dist_q_c";
54pub const CENTROID_DIST_COLUMN: &str = "__centroid_dist";
56pub const PQ_CODE_COLUMN: &str = "__pq_code";
57pub const SQ_CODE_COLUMN: &str = "__sq_code";
58pub const LOSS_METADATA_KEY: &str = "_loss";
59
60pub type PreparedPartitionSearchHandle = Box<dyn Any + Send>;
61
62pub trait PartitionSearchControl: Send + Sync {
64 fn should_stop(&self) -> bool;
65
66 fn record_batch(&self, _batch: &RecordBatch) {}
67}
68
69pub static VECTOR_RESULT_SCHEMA: LazyLock<arrow_schema::SchemaRef> = LazyLock::new(|| {
70 arrow_schema::SchemaRef::new(arrow_schema::Schema::new(vec![
71 Field::new(DIST_COL, arrow_schema::DataType::Float32, true),
72 ROW_ID_FIELD.clone(),
73 ]))
74});
75
76pub static PART_ID_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
77 arrow_schema::Field::new(PART_ID_COLUMN, arrow_schema::DataType::UInt32, true)
78});
79
80pub static CENTROID_DIST_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
81 arrow_schema::Field::new(CENTROID_DIST_COLUMN, arrow_schema::DataType::Float32, true)
82});
83
84pub const DEFAULT_QUERY_PARALLELISM: i32 = 0;
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
91pub enum ApproxMode {
92 Fast,
94
95 #[default]
97 Normal,
98
99 Accurate,
101}
102
103#[derive(Debug, Clone)]
106pub struct Query {
107 pub column: String,
109
110 pub key: ArrayRef,
112
113 pub k: usize,
115
116 pub lower_bound: Option<f32>,
118
119 pub upper_bound: Option<f32>,
121
122 pub minimum_nprobes: usize,
128
129 pub maximum_nprobes: Option<usize>,
132
133 pub ef: Option<usize>,
136
137 pub refine_factor: Option<u32>,
140
141 pub metric_type: Option<DistanceType>,
144
145 pub use_index: bool,
147
148 pub query_parallelism: i32,
158
159 pub dist_q_c: f32,
162
163 pub approx_mode: ApproxMode,
168}
169
170impl From<pb::VectorMetricType> for DistanceType {
171 fn from(proto: pb::VectorMetricType) -> Self {
172 match proto {
173 pb::VectorMetricType::L2 => Self::L2,
174 pb::VectorMetricType::Cosine => Self::Cosine,
175 pb::VectorMetricType::Dot => Self::Dot,
176 pb::VectorMetricType::Hamming => Self::Hamming,
177 }
178 }
179}
180
181impl From<DistanceType> for pb::VectorMetricType {
182 fn from(mt: DistanceType) -> Self {
183 match mt {
184 DistanceType::L2 => Self::L2,
185 DistanceType::Cosine => Self::Cosine,
186 DistanceType::Dot => Self::Dot,
187 DistanceType::Hamming => Self::Hamming,
188 }
189 }
190}
191
192#[async_trait]
200#[allow(clippy::redundant_pub_crate)]
201pub trait VectorIndex: Send + Sync + std::fmt::Debug + Index {
202 async fn search(
219 &self,
220 query: &Query,
221 pre_filter: Arc<dyn PreFilter>,
222 metrics: &dyn MetricsCollector,
223 ) -> Result<RecordBatch>;
224
225 fn find_partitions(&self, query: &Query) -> Result<(UInt32Array, Float32Array)>;
234
235 fn total_partitions(&self) -> usize;
237
238 async fn search_in_partition(
243 &self,
244 partition_id: usize,
245 query: &Query,
246 pre_filter: Arc<dyn PreFilter>,
247 metrics: &dyn MetricsCollector,
248 ) -> Result<RecordBatch>;
249
250 async fn prepare_partition_search(
253 &self,
254 _partition_id: usize,
255 _query: &Query,
256 _pre_filter: Arc<dyn PreFilter>,
257 _metrics: &dyn MetricsCollector,
258 ) -> Result<PreparedPartitionSearchHandle> {
259 unimplemented!("prepared partition search is not supported for this index")
260 }
261
262 fn search_prepared_partition(
264 &self,
265 _prepared: PreparedPartitionSearchHandle,
266 _metrics: &dyn MetricsCollector,
267 ) -> Result<RecordBatch> {
268 unimplemented!("prepared partition search is not supported for this index")
269 }
270
271 fn supports_prepared_partition_search(&self) -> bool {
274 false
275 }
276
277 fn auto_query_parallelism(&self, _cpu_pool_size: usize) -> usize {
283 1
284 }
285
286 #[allow(clippy::too_many_arguments)]
292 async fn search_partitions(
293 self: Arc<Self>,
294 query: Query,
295 partitions: Arc<UInt32Array>,
296 q_c_dists: Arc<Float32Array>,
297 start_idx: usize,
298 end_idx: usize,
299 pre_filter: Arc<dyn PreFilter>,
300 control: Option<Arc<dyn PartitionSearchControl>>,
301 metrics: Arc<dyn MetricsCollector>,
302 ) -> Result<SendableRecordBatchStream>
303 where
304 Self: 'static,
305 {
306 if partitions.len() != q_c_dists.len() {
307 return Err(Error::invalid_input(format!(
308 "partition count {} does not match centroid distance count {}",
309 partitions.len(),
310 q_c_dists.len()
311 )));
312 }
313 if start_idx > end_idx || end_idx > partitions.len() {
314 return Err(Error::invalid_input(format!(
315 "invalid partition search range [{start_idx}, {end_idx}) for {} partitions",
316 partitions.len()
317 )));
318 }
319
320 let stream = stream::try_unfold(start_idx, move |idx| {
321 let index = self.clone();
322 let partitions = partitions.clone();
323 let q_c_dists = q_c_dists.clone();
324 let query = query.clone();
325 let pre_filter = pre_filter.clone();
326 let control = control.clone();
327 let metrics = metrics.clone();
328 async move {
329 if idx >= end_idx
330 || control
331 .as_ref()
332 .is_some_and(|control| control.should_stop())
333 {
334 return Ok(None);
335 }
336 let part_id = partitions.value(idx);
337 let mut query = query;
338 query.dist_q_c = q_c_dists.value(idx);
339 index
340 .search_in_partition(part_id as usize, &query, pre_filter, metrics.as_ref())
341 .await
342 .map(|batch| {
343 if let Some(control) = control.as_ref() {
344 control.record_batch(&batch);
345 }
346 Some((batch, idx + 1))
347 })
348 .map_err(Into::into)
349 }
350 });
351 Ok(Box::pin(RecordBatchStreamAdapter::new(
352 VECTOR_RESULT_SCHEMA.clone(),
353 stream,
354 )))
355 }
356
357 fn is_loadable(&self) -> bool;
360
361 fn use_residual(&self) -> bool;
363
364 async fn load(
369 &self,
370 reader: Arc<dyn Reader>,
371 offset: usize,
372 length: usize,
373 ) -> Result<Box<dyn VectorIndex>>;
374
375 async fn load_partition(
377 &self,
378 reader: Arc<dyn Reader>,
379 offset: usize,
380 length: usize,
381 _partition_id: usize,
382 ) -> Result<Box<dyn VectorIndex>> {
383 self.load(reader, offset, length).await
384 }
385
386 async fn partition_reader(
388 &self,
389 _partition_id: usize,
390 _with_vector: bool,
391 _metrics: &dyn MetricsCollector,
392 ) -> Result<SendableRecordBatchStream> {
393 unimplemented!("only for IVF")
394 }
395
396 async fn to_batch_stream(&self, with_vector: bool) -> Result<SendableRecordBatchStream>;
398
399 fn num_rows(&self) -> u64;
400
401 fn row_ids(&self) -> Box<dyn Iterator<Item = &'_ u64> + '_>;
403
404 async fn remap(&mut self, mapping: &RowAddrRemap) -> Result<()>;
413
414 fn metric_type(&self) -> DistanceType;
416
417 fn ivf_model(&self) -> &IvfModel;
418 fn quantizer(&self) -> Quantizer;
419 fn partition_size(&self, part_id: usize) -> usize;
420
421 fn sub_index_type(&self) -> (SubIndexType, QuantizationType);
423
424 fn open_io_stats(&self) -> Option<lance_io::scheduler::ScanStats> {
429 None
430 }
431}
432
433pub trait VectorIndexCacheEntry: Debug + Send + Sync + DeepSizeOf {
435 fn as_any(&self) -> &dyn Any;
436}