1use arrow::buffer::{OffsetBuffer, ScalarBuffer};
7use arrow_array::{BooleanArray, ListArray, RecordBatch, UInt64Array};
8use arrow_schema::{Field, Schema};
9use async_trait::async_trait;
10use bytes::Bytes;
11use datafusion::functions::regex::regexplike::RegexpLikeFunc;
12use datafusion::functions::string::contains::ContainsFunc;
13use datafusion::functions_nested::array_has;
14use datafusion::physical_plan::SendableRecordBatchStream;
15use datafusion_common::{Column, scalar::ScalarValue};
16use std::collections::{HashMap, HashSet};
17use std::fmt::Debug;
18use std::pin::Pin;
19use std::{any::Any, ops::Bound, sync::Arc};
20
21use datafusion_expr::{Expr, expr::ScalarFunction};
22use inverted::query::{FtsQuery, FtsQueryNode, FtsSearchParams, MatchQuery, fill_fts_query_column};
23use lance_core::deepsize::DeepSizeOf;
24use lance_core::{Error, Result};
25use lance_io::stream::{RecordBatchStream, RecordBatchStreamAdapter};
26use lance_select::{NullableRowAddrSet, RowAddrTreeMap, RowSetOps};
27use roaring::RoaringBitmap;
28use serde::Serialize;
29
30use crate::metrics::MetricsCollector;
31use crate::scalar::registry::TrainingCriteria;
32use crate::{Index, IndexParams, IndexType};
33pub use lance_table::format::IndexFile;
34
35pub mod bitmap;
36pub mod bloomfilter;
37pub mod btree;
38pub mod expression;
39pub mod fmindex;
40pub mod inverted;
41pub mod json;
42pub mod label_list;
43pub mod lance_format;
44pub mod ngram;
45pub mod registry;
46#[cfg(feature = "geo")]
47pub mod rtree;
48pub mod zoned;
49pub mod zonemap;
50
51use crate::frag_reuse::FragReuseIndex;
52pub use inverted::tokenizer::InvertedIndexParams;
53use lance_datafusion::udf::CONTAINS_TOKENS_UDF;
54
55pub const LANCE_SCALAR_INDEX: &str = "__lance_scalar_index";
56
57#[derive(Debug, Clone, PartialEq, Eq, DeepSizeOf)]
63pub enum BuiltinIndexType {
64 BTree,
65 Bitmap,
66 LabelList,
67 NGram,
68 ZoneMap,
69 BloomFilter,
70 RTree,
71 Inverted,
72 Fm,
73}
74
75impl BuiltinIndexType {
76 pub fn as_str(&self) -> &str {
77 match self {
78 Self::BTree => "btree",
79 Self::Bitmap => "bitmap",
80 Self::LabelList => "labellist",
81 Self::NGram => "ngram",
82 Self::ZoneMap => "zonemap",
83 Self::Inverted => "inverted",
84 Self::BloomFilter => "bloomfilter",
85 Self::RTree => "rtree",
86 Self::Fm => "fm",
87 }
88 }
89}
90
91impl TryFrom<IndexType> for BuiltinIndexType {
92 type Error = Error;
93
94 fn try_from(value: IndexType) -> Result<Self> {
95 match value {
96 IndexType::BTree => Ok(Self::BTree),
97 IndexType::Bitmap => Ok(Self::Bitmap),
98 IndexType::LabelList => Ok(Self::LabelList),
99 IndexType::NGram => Ok(Self::NGram),
100 IndexType::ZoneMap => Ok(Self::ZoneMap),
101 IndexType::Inverted => Ok(Self::Inverted),
102 IndexType::BloomFilter => Ok(Self::BloomFilter),
103 IndexType::RTree => Ok(Self::RTree),
104 IndexType::Fm => Ok(Self::Fm),
105 _ => Err(Error::index("Invalid index type".to_string())),
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq)]
111pub struct ScalarIndexParams {
112 pub index_type: String,
116 pub params: Option<String>,
121}
122
123impl Default for ScalarIndexParams {
124 fn default() -> Self {
125 Self {
126 index_type: BuiltinIndexType::BTree.as_str().to_string(),
127 params: None,
128 }
129 }
130}
131
132impl ScalarIndexParams {
133 pub fn for_builtin(index_type: BuiltinIndexType) -> Self {
135 Self {
136 index_type: index_type.as_str().to_string(),
137 params: None,
138 }
139 }
140
141 pub fn new(index_type: String) -> Self {
143 Self {
144 index_type,
145 params: None,
146 }
147 }
148
149 pub fn with_params<ParamsType: Serialize>(mut self, params: &ParamsType) -> Self {
151 self.params = Some(serde_json::to_string(params).unwrap());
152 self
153 }
154}
155
156impl IndexParams for ScalarIndexParams {
157 fn as_any(&self) -> &dyn std::any::Any {
158 self
159 }
160
161 fn index_name(&self) -> &str {
162 LANCE_SCALAR_INDEX
163 }
164}
165
166impl IndexParams for InvertedIndexParams {
167 fn as_any(&self) -> &dyn std::any::Any {
168 self
169 }
170
171 fn index_name(&self) -> &str {
172 "INVERTED"
173 }
174}
175
176#[async_trait]
178pub trait IndexWriter: Send {
179 async fn write_record_batch(&mut self, batch: RecordBatch) -> Result<u64>;
183 async fn add_global_buffer(&mut self, _data: Bytes) -> Result<u32> {
185 Err(Error::not_supported(
186 "global buffers are not supported by this index writer",
187 ))
188 }
189 async fn finish(&mut self) -> Result<IndexFile>;
191 async fn finish_with_metadata(
193 &mut self,
194 metadata: HashMap<String, String>,
195 ) -> Result<IndexFile>;
196}
197
198#[async_trait]
200pub trait IndexReader: Send + Sync {
201 async fn read_record_batch(&self, n: u64, batch_size: u64) -> Result<RecordBatch>;
203 async fn read_global_buffer(&self, _index: u32) -> Result<Bytes> {
205 Err(Error::not_supported(
206 "global buffers are not supported by this index reader",
207 ))
208 }
209 async fn read_range(
214 &self,
215 range: std::ops::Range<usize>,
216 projection: Option<&[&str]>,
217 ) -> Result<RecordBatch>;
218 async fn read_ranges(
221 &self,
222 ranges: &[std::ops::Range<usize>],
223 projection: Option<&[&str]>,
224 ) -> Result<RecordBatch> {
225 if ranges.is_empty() {
226 return self.read_range(0..0, projection).await;
227 }
228 let futures = ranges
229 .iter()
230 .map(|r| self.read_range(r.clone(), projection));
231 let batches = futures::future::try_join_all(futures).await?;
232 let schema = batches[0].schema();
233 Ok(arrow_select::concat::concat_batches(&schema, &batches)?)
234 }
235 async fn read_range_stream(
243 &self,
244 range: std::ops::Range<usize>,
245 projection: Option<&[&str]>,
246 ) -> Result<Pin<Box<dyn RecordBatchStream>>> {
247 let batch = self.read_range(range, projection).await?;
248 let schema = batch.schema();
249 Ok(Box::pin(RecordBatchStreamAdapter::new(
250 schema,
251 futures::stream::once(async move { Ok(batch) }),
252 )))
253 }
254 async fn num_batches(&self, batch_size: u64) -> u32;
256 fn num_rows(&self) -> usize;
258 fn schema(&self) -> &lance_core::datatypes::Schema;
260 fn file_size_bytes(&self) -> Option<u64> {
263 None
264 }
265}
266
267#[async_trait]
273pub trait IndexStore: std::fmt::Debug + Send + Sync + DeepSizeOf {
274 fn as_any(&self) -> &dyn Any;
275 fn clone_arc(&self) -> Arc<dyn IndexStore>;
276
277 fn io_parallelism(&self) -> usize;
279
280 async fn new_index_file(&self, name: &str, schema: Arc<Schema>)
282 -> Result<Box<dyn IndexWriter>>;
283
284 async fn open_index_file(&self, name: &str) -> Result<Arc<dyn IndexReader>>;
286
287 async fn copy_index_file(&self, name: &str, dest_store: &dyn IndexStore) -> Result<IndexFile>;
291
292 async fn copy_index_file_to(
294 &self,
295 name: &str,
296 new_name: &str,
297 dest_store: &dyn IndexStore,
298 ) -> Result<IndexFile> {
299 if name == new_name {
300 self.copy_index_file(name, dest_store).await
301 } else {
302 Err(Error::not_supported(format!(
303 "copying index file {name} to {new_name} is not supported by this index store"
304 )))
305 }
306 }
307
308 async fn rename_index_file(&self, name: &str, new_name: &str) -> Result<IndexFile>;
310
311 async fn delete_index_file(&self, name: &str) -> Result<()>;
313
314 async fn list_files_with_sizes(&self) -> Result<Vec<IndexFile>>;
319}
320
321pub trait AnyQuery: std::fmt::Debug + Any + Send + Sync {
332 fn as_any(&self) -> &dyn Any;
334 fn format(&self, col: &str) -> String;
336 fn to_expr(&self, col: String) -> Expr;
338 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool;
340}
341
342impl PartialEq for dyn AnyQuery {
343 fn eq(&self, other: &Self) -> bool {
344 self.dyn_eq(other)
345 }
346}
347#[derive(Debug, Clone, PartialEq)]
349pub struct FullTextSearchQuery {
350 pub query: FtsQuery,
351
352 pub limit: Option<i64>,
354
355 pub wand_factor: Option<f32>,
360}
361
362impl FullTextSearchQuery {
363 pub fn new(query: String) -> Self {
365 let query = MatchQuery::new(query).into();
366 Self {
367 query,
368 limit: None,
369 wand_factor: None,
370 }
371 }
372
373 pub fn new_fuzzy(term: String, max_distance: Option<u32>) -> Self {
375 let query = MatchQuery::new(term).with_fuzziness(max_distance).into();
376 Self {
377 query,
378 limit: None,
379 wand_factor: None,
380 }
381 }
382
383 pub fn new_query(query: FtsQuery) -> Self {
385 Self {
386 query,
387 limit: None,
388 wand_factor: None,
389 }
390 }
391
392 pub fn with_column(mut self, column: String) -> Result<Self> {
395 self.query = fill_fts_query_column(&self.query, &[column], true)?;
396 Ok(self)
397 }
398
399 pub fn with_columns(mut self, columns: &[String]) -> Result<Self> {
402 self.query = fill_fts_query_column(&self.query, columns, true)?;
403 Ok(self)
404 }
405
406 pub fn limit(mut self, limit: Option<i64>) -> Self {
409 self.limit = limit;
410 self
411 }
412
413 pub fn wand_factor(mut self, wand_factor: Option<f32>) -> Self {
414 self.wand_factor = wand_factor;
415 self
416 }
417
418 pub fn columns(&self) -> HashSet<String> {
419 self.query.columns()
420 }
421
422 pub fn params(&self) -> FtsSearchParams {
423 FtsSearchParams::new()
424 .with_limit(self.limit.map(|limit| limit as usize))
425 .with_wand_factor(self.wand_factor.unwrap_or(1.0))
426 }
427}
428
429#[derive(Debug, Clone, PartialEq)]
439pub enum SargableQuery {
440 Range(Bound<ScalarValue>, Bound<ScalarValue>),
442 IsIn(Vec<ScalarValue>),
444 Equals(ScalarValue),
446 FullTextSearch(FullTextSearchQuery),
448 IsNull(),
450 LikePrefix(ScalarValue),
453}
454
455impl AnyQuery for SargableQuery {
456 fn as_any(&self) -> &dyn Any {
457 self
458 }
459
460 fn format(&self, col: &str) -> String {
461 match self {
462 Self::Range(lower, upper) => match (lower, upper) {
463 (Bound::Unbounded, Bound::Unbounded) => "true".to_string(),
464 (Bound::Unbounded, Bound::Included(rhs)) => format!("{} <= {}", col, rhs),
465 (Bound::Unbounded, Bound::Excluded(rhs)) => format!("{} < {}", col, rhs),
466 (Bound::Included(lhs), Bound::Unbounded) => format!("{} >= {}", col, lhs),
467 (Bound::Included(lhs), Bound::Included(rhs)) => {
468 format!("{} >= {} && {} <= {}", col, lhs, col, rhs)
469 }
470 (Bound::Included(lhs), Bound::Excluded(rhs)) => {
471 format!("{} >= {} && {} < {}", col, lhs, col, rhs)
472 }
473 (Bound::Excluded(lhs), Bound::Unbounded) => format!("{} > {}", col, lhs),
474 (Bound::Excluded(lhs), Bound::Included(rhs)) => {
475 format!("{} > {} && {} <= {}", col, lhs, col, rhs)
476 }
477 (Bound::Excluded(lhs), Bound::Excluded(rhs)) => {
478 format!("{} > {} && {} < {}", col, lhs, col, rhs)
479 }
480 },
481 Self::IsIn(values) => {
482 format!(
483 "{} IN [{}]",
484 col,
485 values
486 .iter()
487 .map(|val| val.to_string())
488 .collect::<Vec<_>>()
489 .join(",")
490 )
491 }
492 Self::FullTextSearch(query) => {
493 format!("fts({})", query.query)
494 }
495 Self::IsNull() => {
496 format!("{} IS NULL", col)
497 }
498 Self::Equals(val) => {
499 format!("{} = {}", col, val)
500 }
501 Self::LikePrefix(prefix) => {
502 format!("{} LIKE '{}%'", col, prefix)
503 }
504 }
505 }
506
507 fn to_expr(&self, col: String) -> Expr {
508 let col_expr = Expr::Column(Column::new_unqualified(col));
509 match self {
510 Self::Range(lower, upper) => match (lower, upper) {
511 (Bound::Unbounded, Bound::Unbounded) => {
512 Expr::Literal(ScalarValue::Boolean(Some(true)), None)
513 }
514 (Bound::Unbounded, Bound::Included(rhs)) => {
515 col_expr.lt_eq(Expr::Literal(rhs.clone(), None))
516 }
517 (Bound::Unbounded, Bound::Excluded(rhs)) => {
518 col_expr.lt(Expr::Literal(rhs.clone(), None))
519 }
520 (Bound::Included(lhs), Bound::Unbounded) => {
521 col_expr.gt_eq(Expr::Literal(lhs.clone(), None))
522 }
523 (Bound::Included(lhs), Bound::Included(rhs)) => col_expr.between(
524 Expr::Literal(lhs.clone(), None),
525 Expr::Literal(rhs.clone(), None),
526 ),
527 (Bound::Included(lhs), Bound::Excluded(rhs)) => col_expr
528 .clone()
529 .gt_eq(Expr::Literal(lhs.clone(), None))
530 .and(col_expr.lt(Expr::Literal(rhs.clone(), None))),
531 (Bound::Excluded(lhs), Bound::Unbounded) => {
532 col_expr.gt(Expr::Literal(lhs.clone(), None))
533 }
534 (Bound::Excluded(lhs), Bound::Included(rhs)) => col_expr
535 .clone()
536 .gt(Expr::Literal(lhs.clone(), None))
537 .and(col_expr.lt_eq(Expr::Literal(rhs.clone(), None))),
538 (Bound::Excluded(lhs), Bound::Excluded(rhs)) => col_expr
539 .clone()
540 .gt(Expr::Literal(lhs.clone(), None))
541 .and(col_expr.lt(Expr::Literal(rhs.clone(), None))),
542 },
543 Self::IsIn(values) => col_expr.in_list(
544 values
545 .iter()
546 .map(|val| Expr::Literal(val.clone(), None))
547 .collect::<Vec<_>>(),
548 false,
549 ),
550 Self::FullTextSearch(query) => col_expr.like(Expr::Literal(
551 ScalarValue::Utf8(Some(query.query.to_string())),
552 None,
553 )),
554 Self::IsNull() => col_expr.is_null(),
555 Self::Equals(value) => col_expr.eq(Expr::Literal(value.clone(), None)),
556 Self::LikePrefix(prefix) => {
557 let pattern = match prefix {
558 ScalarValue::Utf8(Some(s)) => ScalarValue::Utf8(Some(format!("{}%", s))),
559 ScalarValue::LargeUtf8(Some(s)) => {
560 ScalarValue::LargeUtf8(Some(format!("{}%", s)))
561 }
562 other => other.clone(),
563 };
564 col_expr.like(Expr::Literal(pattern, None))
565 }
566 }
567 }
568
569 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
570 match other.as_any().downcast_ref::<Self>() {
571 Some(o) => self == o,
572 None => false,
573 }
574 }
575}
576
577#[derive(Debug, Clone, PartialEq)]
579pub enum LabelListQuery {
580 HasAllLabels(Vec<ScalarValue>),
582 HasAnyLabel(Vec<ScalarValue>),
584}
585
586impl AnyQuery for LabelListQuery {
587 fn as_any(&self) -> &dyn Any {
588 self
589 }
590
591 fn format(&self, col: &str) -> String {
592 format!("{}", self.to_expr(col.to_string()))
593 }
594
595 fn to_expr(&self, col: String) -> Expr {
596 match self {
597 Self::HasAllLabels(labels) => {
598 let labels_arr = ScalarValue::iter_to_array(labels.iter().cloned()).unwrap();
599 let offsets_buffer =
600 OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, labels_arr.len() as i32]));
601 let labels_list = ListArray::try_new(
602 Arc::new(Field::new("item", labels_arr.data_type().clone(), true)),
603 offsets_buffer,
604 labels_arr,
605 None,
606 )
607 .unwrap();
608 let labels_arr = Arc::new(labels_list);
609 Expr::ScalarFunction(ScalarFunction {
610 func: Arc::new(array_has::ArrayHasAll::new().into()),
611 args: vec![
612 Expr::Column(Column::new_unqualified(col)),
613 Expr::Literal(ScalarValue::List(labels_arr), None),
614 ],
615 })
616 }
617 Self::HasAnyLabel(labels) => {
618 let labels_arr = ScalarValue::iter_to_array(labels.iter().cloned()).unwrap();
619 let offsets_buffer =
620 OffsetBuffer::new(ScalarBuffer::<i32>::from(vec![0, labels_arr.len() as i32]));
621 let labels_list = ListArray::try_new(
622 Arc::new(Field::new("item", labels_arr.data_type().clone(), true)),
623 offsets_buffer,
624 labels_arr,
625 None,
626 )
627 .unwrap();
628 let labels_arr = Arc::new(labels_list);
629 Expr::ScalarFunction(ScalarFunction {
630 func: Arc::new(array_has::ArrayHasAny::new().into()),
631 args: vec![
632 Expr::Column(Column::new_unqualified(col)),
633 Expr::Literal(ScalarValue::List(labels_arr), None),
634 ],
635 })
636 }
637 }
638 }
639
640 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
641 match other.as_any().downcast_ref::<Self>() {
642 Some(o) => self == o,
643 None => false,
644 }
645 }
646}
647
648#[derive(Debug, Clone, PartialEq)]
650pub enum TextQuery {
651 StringContains(String),
653 Regex(String),
660 }
663
664impl AnyQuery for TextQuery {
665 fn as_any(&self) -> &dyn Any {
666 self
667 }
668
669 fn format(&self, col: &str) -> String {
670 format!("{}", self.to_expr(col.to_string()))
671 }
672
673 fn to_expr(&self, col: String) -> Expr {
674 match self {
675 Self::StringContains(substr) => Expr::ScalarFunction(ScalarFunction {
676 func: Arc::new(ContainsFunc::new().into()),
677 args: vec![
678 Expr::Column(Column::new_unqualified(col)),
679 Expr::Literal(ScalarValue::Utf8(Some(substr.clone())), None),
680 ],
681 }),
682 Self::Regex(pattern) => Expr::ScalarFunction(ScalarFunction {
687 func: Arc::new(RegexpLikeFunc::new().into()),
688 args: vec![
689 Expr::Column(Column::new_unqualified(col)),
690 Expr::Literal(ScalarValue::Utf8(Some(pattern.clone())), None),
691 ],
692 }),
693 }
694 }
695
696 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
697 match other.as_any().downcast_ref::<Self>() {
698 Some(o) => self == o,
699 None => false,
700 }
701 }
702}
703
704#[derive(Debug, Clone, PartialEq)]
706pub enum TokenQuery {
707 TokensContains(String),
710}
711
712#[derive(Debug, Clone, PartialEq)]
717pub enum BloomFilterQuery {
718 Equals(ScalarValue),
720 IsNull(),
722 IsIn(Vec<ScalarValue>),
724}
725
726impl AnyQuery for BloomFilterQuery {
727 fn as_any(&self) -> &dyn Any {
728 self
729 }
730
731 fn format(&self, col: &str) -> String {
732 match self {
733 Self::Equals(val) => {
734 format!("{} = {}", col, val)
735 }
736 Self::IsNull() => {
737 format!("{} IS NULL", col)
738 }
739 Self::IsIn(values) => {
740 format!(
741 "{} IN [{}]",
742 col,
743 values
744 .iter()
745 .map(|val| val.to_string())
746 .collect::<Vec<_>>()
747 .join(",")
748 )
749 }
750 }
751 }
752
753 fn to_expr(&self, col: String) -> Expr {
754 let col_expr = Expr::Column(Column::new_unqualified(col));
755 match self {
756 Self::Equals(value) => col_expr.eq(Expr::Literal(value.clone(), None)),
757 Self::IsNull() => col_expr.is_null(),
758 Self::IsIn(values) => col_expr.in_list(
759 values
760 .iter()
761 .map(|val| Expr::Literal(val.clone(), None))
762 .collect::<Vec<_>>(),
763 false,
764 ),
765 }
766 }
767
768 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
769 match other.as_any().downcast_ref::<Self>() {
770 Some(o) => self == o,
771 None => false,
772 }
773 }
774}
775
776impl AnyQuery for TokenQuery {
777 fn as_any(&self) -> &dyn Any {
778 self
779 }
780
781 fn format(&self, col: &str) -> String {
782 format!("{}", self.to_expr(col.to_string()))
783 }
784
785 fn to_expr(&self, col: String) -> Expr {
786 match self {
787 Self::TokensContains(substr) => Expr::ScalarFunction(ScalarFunction {
788 func: Arc::new(CONTAINS_TOKENS_UDF.clone()),
789 args: vec![
790 Expr::Column(Column::new_unqualified(col)),
791 Expr::Literal(ScalarValue::Utf8(Some(substr.clone())), None),
792 ],
793 }),
794 }
795 }
796
797 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
798 match other.as_any().downcast_ref::<Self>() {
799 Some(o) => self == o,
800 None => false,
801 }
802 }
803}
804
805#[cfg(feature = "geo")]
806#[derive(Debug, Clone, PartialEq)]
807pub struct RelationQuery {
808 pub value: ScalarValue,
809 pub field: Field,
810}
811
812#[cfg(feature = "geo")]
814#[derive(Debug, Clone, PartialEq)]
815pub enum GeoQuery {
816 IntersectQuery(RelationQuery),
817 IsNull,
818}
819
820#[cfg(feature = "geo")]
821impl AnyQuery for GeoQuery {
822 fn as_any(&self) -> &dyn Any {
823 self
824 }
825
826 fn format(&self, col: &str) -> String {
827 match self {
828 Self::IntersectQuery(query) => {
829 format!("Intersect({} {})", col, query.value)
830 }
831 Self::IsNull => {
832 format!("{} IS NULL", col)
833 }
834 }
835 }
836
837 fn to_expr(&self, _col: String) -> Expr {
838 todo!()
839 }
840
841 fn dyn_eq(&self, other: &dyn AnyQuery) -> bool {
842 match other.as_any().downcast_ref::<Self>() {
843 Some(o) => self == o,
844 None => false,
845 }
846 }
847}
848
849#[derive(Debug, PartialEq)]
851pub enum SearchResult {
852 Exact(NullableRowAddrSet),
854 AtMost(NullableRowAddrSet),
858 AtLeast(NullableRowAddrSet),
863}
864
865impl SearchResult {
866 pub fn exact(row_ids: impl Into<RowAddrTreeMap>) -> Self {
867 Self::Exact(NullableRowAddrSet::new(row_ids.into(), Default::default()))
868 }
869
870 pub fn at_most(row_ids: impl Into<RowAddrTreeMap>) -> Self {
871 Self::AtMost(NullableRowAddrSet::new(row_ids.into(), Default::default()))
872 }
873
874 pub fn at_least(row_ids: impl Into<RowAddrTreeMap>) -> Self {
875 Self::AtLeast(NullableRowAddrSet::new(row_ids.into(), Default::default()))
876 }
877
878 pub fn with_nulls(self, nulls: impl Into<RowAddrTreeMap>) -> Self {
879 match self {
880 Self::Exact(row_ids) => Self::Exact(row_ids.with_nulls(nulls.into())),
881 Self::AtMost(row_ids) => Self::AtMost(row_ids.with_nulls(nulls.into())),
882 Self::AtLeast(row_ids) => Self::AtLeast(row_ids.with_nulls(nulls.into())),
883 }
884 }
885
886 pub fn row_addrs(&self) -> &NullableRowAddrSet {
887 match self {
888 Self::Exact(row_addrs) => row_addrs,
889 Self::AtMost(row_addrs) => row_addrs,
890 Self::AtLeast(row_addrs) => row_addrs,
891 }
892 }
893
894 pub fn is_exact(&self) -> bool {
895 matches!(self, Self::Exact(_))
896 }
897}
898
899pub struct CreatedIndex {
901 pub index_details: prost_types::Any,
906 pub index_version: u32,
910 pub files: Vec<IndexFile>,
915}
916
917pub struct UpdateCriteria {
919 pub requires_old_data: bool,
923 pub data_criteria: TrainingCriteria,
925}
926
927#[derive(Debug, Clone)]
934pub enum OldIndexDataFilter {
935 Fragments {
939 to_keep: RoaringBitmap,
940 to_remove: RoaringBitmap,
941 },
942 RowIds(RowAddrTreeMap),
947}
948
949impl OldIndexDataFilter {
950 pub fn filter_row_ids(&self, row_ids: &UInt64Array) -> BooleanArray {
952 match self {
953 Self::Fragments { to_keep, .. } => row_ids
954 .iter()
955 .map(|id| id.map(|id| to_keep.contains((id >> 32) as u32)))
956 .collect(),
957 Self::RowIds(valid_row_ids) => row_ids
958 .iter()
959 .map(|id| id.map(|id| valid_row_ids.contains(id)))
960 .collect(),
961 }
962 }
963}
964
965impl UpdateCriteria {
966 pub fn requires_old_data(data_criteria: TrainingCriteria) -> Self {
967 Self {
968 requires_old_data: true,
969 data_criteria,
970 }
971 }
972
973 pub fn only_new_data(data_criteria: TrainingCriteria) -> Self {
974 Self {
975 requires_old_data: false,
976 data_criteria,
977 }
978 }
979}
980
981pub fn compute_next_prefix(prefix: &str) -> Option<String> {
1001 if prefix.is_empty() {
1002 return None;
1003 }
1004
1005 let chars: Vec<char> = prefix.chars().collect();
1006
1007 for i in (0..chars.len()).rev() {
1009 if let Some(next_char) = next_unicode_char(chars[i]) {
1010 let mut result: String = chars[..i].iter().collect();
1011 result.push(next_char);
1012 return Some(result);
1013 }
1014 }
1016
1017 None
1019}
1020
1021fn next_unicode_char(c: char) -> Option<char> {
1024 let cp = c as u32;
1025 let next_cp = cp.checked_add(1)?;
1026
1027 let next_cp = if (0xD800..=0xDFFF).contains(&next_cp) {
1029 0xE000
1030 } else {
1031 next_cp
1032 };
1033
1034 char::from_u32(next_cp)
1035}
1036
1037#[async_trait]
1039pub trait ScalarIndex: Send + Sync + std::fmt::Debug + Index + DeepSizeOf {
1040 async fn search(
1044 &self,
1045 query: &dyn AnyQuery,
1046 metrics: &dyn MetricsCollector,
1047 ) -> Result<SearchResult>;
1048
1049 fn can_remap(&self) -> bool;
1051
1052 async fn remap(
1054 &self,
1055 mapping: &HashMap<u64, Option<u64>>,
1056 dest_store: &dyn IndexStore,
1057 ) -> Result<CreatedIndex>;
1058
1059 async fn update(
1064 &self,
1065 new_data: SendableRecordBatchStream,
1066 dest_store: &dyn IndexStore,
1067 old_data_filter: Option<OldIndexDataFilter>,
1068 ) -> Result<CreatedIndex>;
1069
1070 fn update_criteria(&self) -> UpdateCriteria;
1072
1073 fn derive_index_params(&self) -> Result<ScalarIndexParams>;
1078}