lance_index/scalar/registry.rs
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use std::sync::Arc;
5
6use arrow_schema::Field;
7use async_trait::async_trait;
8use datafusion::execution::SendableRecordBatchStream;
9use lance_core::{Result, cache::LanceCache};
10
11use crate::progress::IndexBuildProgress;
12use crate::registry::IndexPluginRegistry;
13use crate::{
14 frag_reuse::FragReuseIndex,
15 scalar::{CreatedIndex, IndexStore, ScalarIndex, expression::ScalarQueryParser},
16};
17
18pub const VALUE_COLUMN_NAME: &str = "value";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum TrainingOrdering {
22 /// The input will arrive sorted by the value column in ascending order
23 Values,
24 /// The input will arrive sorted by the address column in ascending order
25 Addresses,
26 /// The input will arrive in an arbitrary order
27 None,
28}
29
30#[derive(Debug, Clone)]
31pub struct TrainingCriteria {
32 pub ordering: TrainingOrdering,
33 pub needs_row_ids: bool,
34 pub needs_row_addrs: bool,
35}
36
37impl TrainingCriteria {
38 pub fn new(ordering: TrainingOrdering) -> Self {
39 Self {
40 ordering,
41 needs_row_ids: false,
42 needs_row_addrs: false,
43 }
44 }
45
46 pub fn with_row_id(mut self) -> Self {
47 self.needs_row_ids = true;
48 self
49 }
50
51 pub fn with_row_addr(mut self) -> Self {
52 self.needs_row_addrs = true;
53 self
54 }
55}
56
57/// A trait that describes what criteria is needed to train an index
58///
59/// The training process has two steps. First, the parameters are given to the
60/// plugin and it creates a TrainingRequest. Then, the caller prepares the training
61/// data and calls train_index.
62///
63/// The call to train_index will include the training request. This allows the plugin
64/// to stash any deserialized parameter info in the request and fetch it later during
65/// training by downcasting to the appropriate type.
66pub trait TrainingRequest: std::any::Any + Send + Sync {
67 fn as_any(&self) -> &dyn std::any::Any;
68 fn criteria(&self) -> &TrainingCriteria;
69}
70
71/// A default training request impl for indexes that don't need any parameters
72pub(crate) struct DefaultTrainingRequest {
73 criteria: TrainingCriteria,
74}
75
76impl DefaultTrainingRequest {
77 pub fn new(criteria: TrainingCriteria) -> Self {
78 Self { criteria }
79 }
80}
81
82impl TrainingRequest for DefaultTrainingRequest {
83 fn as_any(&self) -> &dyn std::any::Any {
84 self
85 }
86
87 fn criteria(&self) -> &TrainingCriteria {
88 &self.criteria
89 }
90}
91
92/// A trait for scalar index plugins
93#[async_trait]
94pub trait ScalarIndexPlugin: Send + Sync + std::fmt::Debug {
95 /// Creates a new training request from the given parameters
96 ///
97 /// This training request specifies the criteria that the data must satisfy to train the index.
98 /// For example, does the index require the input data to be sorted?
99 fn new_training_request(&self, params: &str, field: &Field)
100 -> Result<Box<dyn TrainingRequest>>;
101
102 /// Train a new index
103 ///
104 /// The provided data must fulfill all the criteria returned by `training_criteria`.
105 /// It is the caller's responsibility to ensure this.
106 ///
107 /// Returns index details that describe the index. These details can potentially be
108 /// useful for planning (although this will currently require inside information on
109 /// the index type) and they will need to be provided when loading the index.
110 ///
111 /// It is the caller's responsibility to store these details somewhere.
112 async fn train_index(
113 &self,
114 data: SendableRecordBatchStream,
115 index_store: &dyn IndexStore,
116 request: Box<dyn TrainingRequest>,
117 fragment_ids: Option<Vec<u32>>,
118 progress: Arc<dyn IndexBuildProgress>,
119 ) -> Result<CreatedIndex>;
120
121 /// A short name for the index
122 ///
123 /// This is a friendly name for display purposes and also can be used as an alias for
124 /// the index type URL. If multiple plugins have the same name, then the first one
125 /// found will be used.
126 ///
127 /// By convention this is MixedCase with no spaces. When used as an alias, it will be
128 /// compared case-insensitively.
129 fn name(&self) -> &str;
130
131 /// Returns true if the index returns an exact answer (e.g. not AtMost)
132 fn provides_exact_answer(&self) -> bool;
133
134 /// The version of the index plugin
135 ///
136 /// We assume that indexes are not forwards compatible. If an index was written with a
137 /// newer version than this, it cannot be read
138 fn version(&self) -> u32;
139
140 /// Returns a new query parser for the index
141 ///
142 /// Can return None if this index cannot participate in query optimization
143 fn new_query_parser(
144 &self,
145 index_name: String,
146 index_details: &prost_types::Any,
147 ) -> Option<Box<dyn ScalarQueryParser>>;
148
149 /// Load an index from storage
150 ///
151 /// The index details should match the details that were returned when the index was
152 /// originally trained.
153 async fn load_index(
154 &self,
155 index_store: Arc<dyn IndexStore>,
156 index_details: &prost_types::Any,
157 frag_reuse_index: Option<Arc<FragReuseIndex>>,
158 cache: &LanceCache,
159 ) -> Result<Arc<dyn ScalarIndex>>;
160
161 /// Optional hook allowing a plugin to provide statistics without loading the index.
162 async fn load_statistics(
163 &self,
164 _index_store: Arc<dyn IndexStore>,
165 _index_details: &prost_types::Any,
166 ) -> Result<Option<serde_json::Value>> {
167 Ok(None)
168 }
169
170 /// Optional hook that plugins can use if they need to be aware of the registry
171 fn attach_registry(&self, _registry: Arc<IndexPluginRegistry>) {}
172
173 /// Returns a JSON string representation of the provided index details
174 ///
175 /// These details will be user-visible and should be considered part of the public
176 /// API. As a result, efforts should be made to ensure the information is backwards
177 /// compatible and avoid breaking changes.
178 fn details_as_json(&self, _details: &prost_types::Any) -> Result<serde_json::Value> {
179 // Return an empty JSON object as the default implementation
180 Ok(serde_json::json!({}))
181 }
182}