1use std::{any::Any, collections::HashMap, fmt::Debug, pin::Pin, sync::Arc};
5
6use arrow::array::AsArray;
7use arrow_array::{Array, RecordBatch, UInt64Array};
8use arrow_schema::{DataType, Field, Fields, Schema, SchemaRef};
9use async_trait::async_trait;
10use datafusion::execution::RecordBatchStream;
11use datafusion::physical_plan::{SendableRecordBatchStream, stream::RecordBatchStreamAdapter};
12use datafusion_common::ScalarValue;
13use deepsize::DeepSizeOf;
14use futures::{StreamExt, TryStream, TryStreamExt, stream::BoxStream};
15use lance_core::cache::LanceCache;
16use lance_core::utils::mask::{NullableRowAddrSet, RowAddrTreeMap};
17use lance_core::{Error, Result};
18use roaring::RoaringBitmap;
19use tracing::instrument;
20
21use super::{AnyQuery, IndexStore, LabelListQuery, ScalarIndex, bitmap::BitmapIndex};
22use super::{BuiltinIndexType, SargableQuery, ScalarIndexParams};
23use super::{MetricsCollector, SearchResult};
24use crate::frag_reuse::FragReuseIndex;
25use crate::pbold;
26use crate::scalar::bitmap::BitmapIndexPlugin;
27use crate::scalar::expression::{LabelListQueryParser, ScalarQueryParser};
28use crate::scalar::registry::{
29 DefaultTrainingRequest, ScalarIndexPlugin, TrainingCriteria, TrainingOrdering, TrainingRequest,
30 VALUE_COLUMN_NAME,
31};
32use crate::scalar::{CreatedIndex, UpdateCriteria};
33use crate::{Index, IndexType};
34
35pub const BITMAP_LOOKUP_NAME: &str = "bitmap_page_lookup.lance";
36const LABEL_LIST_INDEX_VERSION: u32 = 0;
37
38#[async_trait]
39trait LabelListSubIndex: ScalarIndex + DeepSizeOf {
40 async fn search_exact(
41 &self,
42 query: &dyn AnyQuery,
43 metrics: &dyn MetricsCollector,
44 ) -> Result<NullableRowAddrSet> {
45 let result = self.search(query, metrics).await?;
46 match result {
47 SearchResult::Exact(row_ids) => {
48 Ok(row_ids.with_nulls(RowAddrTreeMap::new()))
52 }
53 _ => Err(Error::internal(
54 "Label list sub-index should return exact results".to_string(),
55 )),
56 }
57 }
58}
59
60impl<T: ScalarIndex + DeepSizeOf> LabelListSubIndex for T {}
61
62#[derive(Clone, Debug, DeepSizeOf)]
66pub struct LabelListIndex {
67 values_index: Arc<dyn LabelListSubIndex>,
68}
69
70impl LabelListIndex {
71 fn new(values_index: Arc<dyn LabelListSubIndex>) -> Self {
72 Self { values_index }
73 }
74
75 async fn load(
76 store: Arc<dyn IndexStore>,
77 frag_reuse_index: Option<Arc<FragReuseIndex>>,
78 index_cache: &LanceCache,
79 ) -> Result<Arc<Self>> {
80 BitmapIndex::load(store, frag_reuse_index, index_cache)
81 .await
82 .map(|index| Arc::new(Self::new(index)))
83 }
84}
85
86#[async_trait]
87impl Index for LabelListIndex {
88 fn as_any(&self) -> &dyn Any {
89 self
90 }
91
92 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
93 self
94 }
95
96 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn crate::vector::VectorIndex>> {
97 Err(Error::not_supported_source(
98 "LabeListIndex is not a vector index".into(),
99 ))
100 }
101
102 async fn prewarm(&self) -> Result<()> {
103 self.values_index.prewarm().await
104 }
105
106 fn index_type(&self) -> IndexType {
107 IndexType::LabelList
108 }
109
110 fn statistics(&self) -> Result<serde_json::Value> {
111 self.values_index.statistics()
112 }
113
114 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
115 unimplemented!()
116 }
117}
118
119impl LabelListIndex {
120 fn search_values<'a>(
121 &'a self,
122 values: &'a Vec<ScalarValue>,
123 metrics: &'a dyn MetricsCollector,
124 ) -> BoxStream<'a, Result<NullableRowAddrSet>> {
125 futures::stream::iter(values)
126 .then(move |value| {
127 let value_query = SargableQuery::Equals(value.clone());
128 async move { self.values_index.search_exact(&value_query, metrics).await }
129 })
130 .boxed()
131 }
132
133 async fn set_union<'a>(
134 &'a self,
135 mut sets: impl TryStream<Ok = NullableRowAddrSet, Error = Error> + 'a + Unpin,
136 single_set: bool,
137 ) -> Result<NullableRowAddrSet> {
138 let mut union_bitmap = sets.try_next().await?.unwrap();
139 if single_set {
140 return Ok(union_bitmap);
141 }
142 while let Some(next) = sets.try_next().await? {
143 union_bitmap |= &next;
144 }
145 Ok(union_bitmap)
146 }
147
148 async fn set_intersection<'a>(
149 &'a self,
150 mut sets: impl TryStream<Ok = NullableRowAddrSet, Error = Error> + 'a + Unpin,
151 single_set: bool,
152 ) -> Result<NullableRowAddrSet> {
153 let mut intersect_bitmap = sets.try_next().await?.unwrap();
154 if single_set {
155 return Ok(intersect_bitmap);
156 }
157 while let Some(next) = sets.try_next().await? {
158 intersect_bitmap &= &next;
159 }
160 Ok(intersect_bitmap)
161 }
162}
163
164#[async_trait]
165impl ScalarIndex for LabelListIndex {
166 #[instrument(skip_all, level = "debug")]
167 async fn search(
168 &self,
169 query: &dyn AnyQuery,
170 metrics: &dyn MetricsCollector,
171 ) -> Result<SearchResult> {
172 let query = query.as_any().downcast_ref::<LabelListQuery>().unwrap();
173
174 let row_ids = match query {
175 LabelListQuery::HasAllLabels(labels) => {
176 let values_results = self.search_values(labels, metrics);
177 self.set_intersection(values_results, labels.len() == 1)
178 .await
179 }
180 LabelListQuery::HasAnyLabel(labels) => {
181 let values_results = self.search_values(labels, metrics);
182 self.set_union(values_results, labels.len() == 1).await
183 }
184 }?;
185 Ok(SearchResult::Exact(row_ids))
186 }
187
188 fn can_remap(&self) -> bool {
189 true
190 }
191
192 async fn remap(
194 &self,
195 mapping: &HashMap<u64, Option<u64>>,
196 dest_store: &dyn IndexStore,
197 ) -> Result<CreatedIndex> {
198 self.values_index.remap(mapping, dest_store).await?;
199
200 Ok(CreatedIndex {
201 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
202 .unwrap(),
203 index_version: LABEL_LIST_INDEX_VERSION,
204 })
205 }
206
207 async fn update(
209 &self,
210 new_data: SendableRecordBatchStream,
211 dest_store: &dyn IndexStore,
212 valid_old_fragments: Option<&RoaringBitmap>,
213 ) -> Result<CreatedIndex> {
214 self.values_index
215 .update(unnest_chunks(new_data)?, dest_store, valid_old_fragments)
216 .await?;
217
218 Ok(CreatedIndex {
219 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
220 .unwrap(),
221 index_version: LABEL_LIST_INDEX_VERSION,
222 })
223 }
224
225 fn update_criteria(&self) -> UpdateCriteria {
226 UpdateCriteria::only_new_data(TrainingCriteria::new(TrainingOrdering::None).with_row_id())
227 }
228
229 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
230 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::LabelList))
231 }
232}
233
234fn extract_flatten_indices(list_arr: &dyn Array) -> UInt64Array {
235 if let Some(list_arr) = list_arr.as_list_opt::<i32>() {
236 let mut indices = Vec::with_capacity(list_arr.values().len());
237 let offsets = list_arr.value_offsets();
238 for (offset_idx, w) in offsets.windows(2).enumerate() {
239 let size = (w[1] - w[0]) as u64;
240 indices.extend((0..size).map(|_| offset_idx as u64));
241 }
242 UInt64Array::from(indices)
243 } else if let Some(list_arr) = list_arr.as_list_opt::<i64>() {
244 let mut indices = Vec::with_capacity(list_arr.values().len());
245 let offsets = list_arr.value_offsets();
246 for (offset_idx, w) in offsets.windows(2).enumerate() {
247 let size = (w[1] - w[0]) as u64;
248 indices.extend((0..size).map(|_| offset_idx as u64));
249 }
250 UInt64Array::from(indices)
251 } else {
252 unreachable!(
253 "Should verify that the first column is a list earlier. Got array of type: {}",
254 list_arr.data_type()
255 )
256 }
257}
258
259fn unnest_schema(schema: &Schema) -> SchemaRef {
260 let mut fields_iter = schema.fields.iter().cloned();
261 let key_field = fields_iter.next().unwrap();
262 let remaining_fields = fields_iter.collect::<Vec<_>>();
263
264 let new_key_field = match key_field.data_type() {
265 DataType::List(item_field) | DataType::LargeList(item_field) => Field::new(
266 key_field.name(),
267 item_field.data_type().clone(),
268 item_field.is_nullable() || key_field.is_nullable(),
269 ),
270 other_type => {
271 unreachable!(
272 "The first field in the schema must be a List or LargeList type. \
273 Found: {}. This should have been verified earlier in the code.",
274 other_type
275 )
276 }
277 };
278
279 let all_fields = vec![Arc::new(new_key_field)]
280 .into_iter()
281 .chain(remaining_fields)
282 .collect::<Vec<_>>();
283
284 Arc::new(Schema::new(Fields::from(all_fields)))
285}
286
287fn unnest_batch(
288 batch: arrow::record_batch::RecordBatch,
289 unnest_schema: SchemaRef,
290) -> datafusion_common::Result<RecordBatch> {
291 let mut columns_iter = batch.columns().iter().cloned();
292 let key_col = columns_iter.next().unwrap();
293 let remaining_cols = columns_iter.collect::<Vec<_>>();
294
295 let remaining_fields = unnest_schema
296 .fields
297 .iter()
298 .skip(1)
299 .cloned()
300 .collect::<Vec<_>>();
301
302 let remaining_batch = RecordBatch::try_new(
303 Arc::new(Schema::new(Fields::from(remaining_fields))),
304 remaining_cols,
305 )?;
306
307 let flatten_indices = extract_flatten_indices(key_col.as_ref());
308
309 let flattened_remaining =
310 arrow_select::take::take_record_batch(&remaining_batch, &flatten_indices)?;
311
312 let new_key_values = if let Some(key_list) = key_col.as_list_opt::<i32>() {
313 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
314 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
315 key_list
316 .values()
317 .slice(value_start, value_stop - value_start)
318 .clone()
319 } else if let Some(key_list) = key_col.as_list_opt::<i64>() {
320 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
321 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
322 key_list
323 .values()
324 .slice(value_start, value_stop - value_start)
325 .clone()
326 } else {
327 unreachable!("Should verify that the first column is a list earlier")
328 };
329
330 let all_columns = vec![new_key_values]
331 .into_iter()
332 .chain(flattened_remaining.columns().iter().cloned())
333 .collect::<Vec<_>>();
334
335 datafusion_common::Result::Ok(arrow::record_batch::RecordBatch::try_new(
336 unnest_schema,
337 all_columns,
338 )?)
339}
340
341fn unnest_chunks(
342 source: Pin<Box<dyn RecordBatchStream + Send>>,
343) -> Result<SendableRecordBatchStream> {
344 let unnest_schema = unnest_schema(source.schema().as_ref());
345 let unnest_schema_copy = unnest_schema.clone();
346 let source = source.try_filter_map(move |batch| {
347 std::future::ready(Some(unnest_batch(batch, unnest_schema.clone())).transpose())
348 });
349
350 Ok(Box::pin(RecordBatchStreamAdapter::new(
351 unnest_schema_copy,
352 source,
353 )))
354}
355
356#[derive(Debug, Default)]
357pub struct LabelListIndexPlugin;
358
359#[async_trait]
360impl ScalarIndexPlugin for LabelListIndexPlugin {
361 fn name(&self) -> &str {
362 "LabelList"
363 }
364
365 fn new_training_request(
366 &self,
367 _params: &str,
368 field: &Field,
369 ) -> Result<Box<dyn TrainingRequest>> {
370 if !matches!(
371 field.data_type(),
372 DataType::List(_) | DataType::LargeList(_)
373 ) {
374 return Err(Error::invalid_input_source(format!(
375 "LabelList index can only be created on List or LargeList type columns. Column has type {:?}",
376 field.data_type()
377 )
378 .into()));
379 }
380
381 Ok(Box::new(DefaultTrainingRequest::new(
382 TrainingCriteria::new(TrainingOrdering::None).with_row_id(),
383 )))
384 }
385
386 fn provides_exact_answer(&self) -> bool {
387 true
388 }
389
390 fn version(&self) -> u32 {
391 LABEL_LIST_INDEX_VERSION
392 }
393
394 fn new_query_parser(
395 &self,
396 index_name: String,
397 _index_details: &prost_types::Any,
398 ) -> Option<Box<dyn ScalarQueryParser>> {
399 Some(Box::new(LabelListQueryParser::new(index_name)))
400 }
401
402 async fn train_index(
407 &self,
408 data: SendableRecordBatchStream,
409 index_store: &dyn IndexStore,
410 request: Box<dyn TrainingRequest>,
411 fragment_ids: Option<Vec<u32>>,
412 progress: Arc<dyn crate::progress::IndexBuildProgress>,
413 ) -> Result<CreatedIndex> {
414 if fragment_ids.is_some() {
415 return Err(Error::invalid_input_source(
416 "LabelList index does not support fragment training".into(),
417 ));
418 }
419
420 let schema = data.schema();
421 let field = schema
422 .column_with_name(VALUE_COLUMN_NAME)
423 .ok_or_else(|| {
424 Error::invalid_input_source(
425 "Index training data missing value column"
426 .to_string()
427 .into(),
428 )
429 })?
430 .1;
431
432 if !matches!(
433 field.data_type(),
434 DataType::List(_) | DataType::LargeList(_)
435 ) {
436 return Err(Error::invalid_input_source(format!(
437 "LabelList index can only be created on List or LargeList type columns. Column has type {:?}",
438 field.data_type()
439 )
440 .into()));
441 }
442
443 let data = unnest_chunks(data)?;
444 let bitmap_plugin = BitmapIndexPlugin;
445 bitmap_plugin
446 .train_index(data, index_store, request, fragment_ids, progress)
447 .await?;
448 Ok(CreatedIndex {
449 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
450 .unwrap(),
451 index_version: LABEL_LIST_INDEX_VERSION,
452 })
453 }
454
455 async fn load_index(
457 &self,
458 index_store: Arc<dyn IndexStore>,
459 _index_details: &prost_types::Any,
460 frag_reuse_index: Option<Arc<FragReuseIndex>>,
461 cache: &LanceCache,
462 ) -> Result<Arc<dyn ScalarIndex>> {
463 Ok(
464 LabelListIndex::load(index_store, frag_reuse_index, cache).await?
465 as Arc<dyn ScalarIndex>,
466 )
467 }
468}