1use std::{
5 any::Any,
6 collections::HashMap,
7 fmt::Debug,
8 pin::Pin,
9 sync::{Arc, Mutex},
10};
11
12use arrow::array::AsArray;
13use arrow_array::{Array, RecordBatch, UInt64Array};
14use arrow_schema::{DataType, Field, Fields, Schema, SchemaRef};
15use async_trait::async_trait;
16use bytes::Bytes;
17use datafusion::execution::RecordBatchStream;
18use datafusion::physical_plan::{SendableRecordBatchStream, stream::RecordBatchStreamAdapter};
19use datafusion_common::ScalarValue;
20use deepsize::DeepSizeOf;
21use futures::{StreamExt, TryStream, TryStreamExt, stream::BoxStream};
22use lance_core::cache::LanceCache;
23use lance_core::error::LanceOptionExt;
24use lance_core::utils::mask::{NullableRowAddrSet, RowAddrTreeMap, RowSetOps};
25use lance_core::{Error, ROW_ID, Result};
26use roaring::RoaringBitmap;
27use tracing::instrument;
28
29use super::{AnyQuery, IndexStore, LabelListQuery, ScalarIndex, bitmap::BitmapIndex};
30use super::{BuiltinIndexType, SargableQuery, ScalarIndexParams};
31use super::{MetricsCollector, SearchResult};
32use crate::frag_reuse::FragReuseIndex;
33use crate::pbold;
34use crate::scalar::bitmap::BitmapIndexPlugin;
35use crate::scalar::expression::{LabelListQueryParser, ScalarQueryParser};
36use crate::scalar::registry::{
37 DefaultTrainingRequest, ScalarIndexPlugin, TrainingCriteria, TrainingOrdering, TrainingRequest,
38 VALUE_COLUMN_NAME,
39};
40use crate::scalar::{CreatedIndex, UpdateCriteria};
41use crate::{Index, IndexType};
42
43pub const BITMAP_LOOKUP_NAME: &str = "bitmap_page_lookup.lance";
44pub const LABEL_LIST_NULLS_METADATA_KEY: &str = "lance:label_list_nulls";
45pub const LABEL_LIST_NULLS_MIN_VERSION: i32 = 1;
46const LABEL_LIST_INDEX_VERSION: u32 = 1;
47
48#[async_trait]
49trait LabelListSubIndex: ScalarIndex + DeepSizeOf {
50 async fn search_exact(
51 &self,
52 query: &dyn AnyQuery,
53 metrics: &dyn MetricsCollector,
54 ) -> Result<NullableRowAddrSet> {
55 let result = self.search(query, metrics).await?;
56 match result {
57 SearchResult::Exact(row_ids) => {
58 Ok(row_ids.with_nulls(RowAddrTreeMap::new()))
62 }
63 _ => Err(Error::internal(
64 "Label list sub-index should return exact results".to_string(),
65 )),
66 }
67 }
68}
69
70impl<T: ScalarIndex + DeepSizeOf> LabelListSubIndex for T {}
71
72#[derive(Clone, Debug, DeepSizeOf)]
76pub struct LabelListIndex {
77 values_index: Arc<BitmapIndex>,
78 list_nulls: Arc<RowAddrTreeMap>,
79}
80
81impl LabelListIndex {
82 fn new(values_index: Arc<BitmapIndex>, list_nulls: Arc<RowAddrTreeMap>) -> Self {
83 Self {
84 values_index,
85 list_nulls,
86 }
87 }
88
89 async fn load(
90 store: Arc<dyn IndexStore>,
91 frag_reuse_index: Option<Arc<FragReuseIndex>>,
92 index_cache: &LanceCache,
93 ) -> Result<Arc<Self>> {
94 let values_index =
95 BitmapIndex::load(store.clone(), frag_reuse_index.clone(), index_cache).await?;
96 let list_nulls = read_list_nulls(store, frag_reuse_index).await?;
97 Ok(Arc::new(Self::new(values_index, Arc::new(list_nulls))))
98 }
99}
100
101#[async_trait]
102impl Index for LabelListIndex {
103 fn as_any(&self) -> &dyn Any {
104 self
105 }
106
107 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
108 self
109 }
110
111 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn crate::vector::VectorIndex>> {
112 Err(Error::not_supported_source(
113 "LabeListIndex is not a vector index".into(),
114 ))
115 }
116
117 async fn prewarm(&self) -> Result<()> {
118 self.values_index.prewarm().await
119 }
120
121 fn index_type(&self) -> IndexType {
122 IndexType::LabelList
123 }
124
125 fn statistics(&self) -> Result<serde_json::Value> {
126 self.values_index.statistics()
127 }
128
129 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
130 unimplemented!()
131 }
132}
133
134impl LabelListIndex {
135 fn search_values<'a>(
136 &'a self,
137 values: &'a Vec<ScalarValue>,
138 metrics: &'a dyn MetricsCollector,
139 ) -> BoxStream<'a, Result<NullableRowAddrSet>> {
140 futures::stream::iter(values)
141 .then(move |value| {
142 let value_query = SargableQuery::Equals(value.clone());
143 async move { self.values_index.search_exact(&value_query, metrics).await }
144 })
145 .boxed()
146 }
147
148 async fn set_union<'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 union_bitmap = sets.try_next().await?.unwrap();
154 if single_set {
155 return Ok(union_bitmap);
156 }
157 while let Some(next) = sets.try_next().await? {
158 union_bitmap |= &next;
159 }
160 Ok(union_bitmap)
161 }
162
163 async fn set_intersection<'a>(
164 &'a self,
165 mut sets: impl TryStream<Ok = NullableRowAddrSet, Error = Error> + 'a + Unpin,
166 single_set: bool,
167 ) -> Result<NullableRowAddrSet> {
168 let mut intersect_bitmap = sets.try_next().await?.unwrap();
169 if single_set {
170 return Ok(intersect_bitmap);
171 }
172 while let Some(next) = sets.try_next().await? {
173 intersect_bitmap &= &next;
174 }
175 Ok(intersect_bitmap)
176 }
177}
178
179#[async_trait]
180impl ScalarIndex for LabelListIndex {
181 #[instrument(skip_all, level = "debug")]
182 async fn search(
183 &self,
184 query: &dyn AnyQuery,
185 metrics: &dyn MetricsCollector,
186 ) -> Result<SearchResult> {
187 let query = query.as_any().downcast_ref::<LabelListQuery>().unwrap();
188
189 let row_ids = match query {
190 LabelListQuery::HasAllLabels(labels) => {
191 let values_results = self.search_values(labels, metrics);
192 self.set_intersection(values_results, labels.len() == 1)
193 .await
194 }
195 LabelListQuery::HasAnyLabel(labels) => {
196 let values_results = self.search_values(labels, metrics);
197 self.set_union(values_results, labels.len() == 1).await
198 }
199 }?;
200 let row_ids = if self.list_nulls.as_ref().is_empty() {
201 row_ids
202 } else {
203 let mut nulls = row_ids.null_rows().clone();
204 nulls |= self.list_nulls.as_ref();
205 row_ids.with_nulls(nulls)
206 };
207 Ok(SearchResult::Exact(row_ids))
208 }
209
210 fn can_remap(&self) -> bool {
211 true
212 }
213
214 async fn remap(
216 &self,
217 mapping: &HashMap<u64, Option<u64>>,
218 dest_store: &dyn IndexStore,
219 ) -> Result<CreatedIndex> {
220 let state = self.values_index.load_bitmap_index_state().await?;
221 let remapped_state = BitmapIndexPlugin::remap_bitmap_state(state, mapping);
222 let remapped_nulls =
223 RowAddrTreeMap::from_iter(self.list_nulls.row_addrs().unwrap().filter_map(|addr| {
224 let addr_as_u64 = u64::from(addr);
225 mapping
226 .get(&addr_as_u64)
227 .copied()
228 .unwrap_or(Some(addr_as_u64))
229 }));
230 write_label_list_bitmap_index(
231 remapped_state,
232 dest_store,
233 self.values_index.value_type(),
234 &remapped_nulls,
235 )
236 .await?;
237
238 Ok(CreatedIndex {
239 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
240 .unwrap(),
241 index_version: LABEL_LIST_INDEX_VERSION,
242 files: Some(dest_store.list_files_with_sizes().await?),
243 })
244 }
245
246 async fn update(
248 &self,
249 new_data: SendableRecordBatchStream,
250 dest_store: &dyn IndexStore,
251 old_data_filter: Option<super::OldIndexDataFilter>,
252 ) -> Result<CreatedIndex> {
253 let state = self.values_index.load_bitmap_index_state().await?;
254 let list_nulls = Arc::new(Mutex::new(RowAddrTreeMap::new()));
255 let new_data = track_list_nulls(new_data, list_nulls.clone());
256 let (merged_state, value_type) =
257 BitmapIndexPlugin::build_bitmap_index_state(unnest_chunks(new_data)?, state).await?;
258 let _ = old_data_filter;
259 let mut merged_nulls = (*self.list_nulls).clone();
260 let new_nulls = list_nulls.lock().unwrap().clone();
261 if !new_nulls.is_empty() {
262 merged_nulls |= &new_nulls;
263 }
264 write_label_list_bitmap_index(merged_state, dest_store, &value_type, &merged_nulls).await?;
265
266 Ok(CreatedIndex {
267 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
268 .unwrap(),
269 index_version: LABEL_LIST_INDEX_VERSION,
270 files: Some(dest_store.list_files_with_sizes().await?),
271 })
272 }
273
274 fn update_criteria(&self) -> UpdateCriteria {
275 UpdateCriteria::only_new_data(TrainingCriteria::new(TrainingOrdering::None).with_row_id())
276 }
277
278 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
279 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::LabelList))
280 }
281}
282
283fn extract_flatten_indices(list_arr: &dyn Array) -> UInt64Array {
284 if let Some(list_arr) = list_arr.as_list_opt::<i32>() {
285 let mut indices = Vec::with_capacity(list_arr.values().len());
286 let offsets = list_arr.value_offsets();
287 for (offset_idx, w) in offsets.windows(2).enumerate() {
288 let size = (w[1] - w[0]) as u64;
289 indices.extend((0..size).map(|_| offset_idx as u64));
290 }
291 UInt64Array::from(indices)
292 } else if let Some(list_arr) = list_arr.as_list_opt::<i64>() {
293 let mut indices = Vec::with_capacity(list_arr.values().len());
294 let offsets = list_arr.value_offsets();
295 for (offset_idx, w) in offsets.windows(2).enumerate() {
296 let size = (w[1] - w[0]) as u64;
297 indices.extend((0..size).map(|_| offset_idx as u64));
298 }
299 UInt64Array::from(indices)
300 } else {
301 unreachable!(
302 "Should verify that the first column is a list earlier. Got array of type: {}",
303 list_arr.data_type()
304 )
305 }
306}
307
308fn track_list_nulls(
310 source: SendableRecordBatchStream,
311 list_nulls: Arc<Mutex<RowAddrTreeMap>>,
312) -> SendableRecordBatchStream {
313 let schema = source.schema();
314 let stream = source.try_filter_map(move |batch| {
315 let list_nulls = list_nulls.clone();
316 async move {
317 record_list_nulls(&batch, &list_nulls)?;
318 Ok(Some(batch))
319 }
320 });
321
322 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
323}
324
325fn record_list_nulls(
326 batch: &RecordBatch,
327 list_nulls: &Arc<Mutex<RowAddrTreeMap>>,
328) -> datafusion_common::Result<()> {
329 let values = batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?;
330 let row_ids = batch.column_by_name(ROW_ID).expect_ok()?;
331 let row_ids = row_ids.as_any().downcast_ref::<UInt64Array>().unwrap();
332
333 let mut local_nulls = RowAddrTreeMap::new();
334 for i in 0..values.len() {
335 if values.is_null(i) {
336 local_nulls.insert(row_ids.value(i));
337 }
338 }
339 if !local_nulls.is_empty() {
340 let mut guard = list_nulls.lock().unwrap();
341 *guard |= &local_nulls;
342 }
343 Ok(())
344}
345
346fn unnest_schema(schema: &Schema) -> SchemaRef {
347 let mut fields_iter = schema.fields.iter().cloned();
348 let key_field = fields_iter.next().unwrap();
349 let remaining_fields = fields_iter.collect::<Vec<_>>();
350
351 let new_key_field = match key_field.data_type() {
352 DataType::List(item_field) | DataType::LargeList(item_field) => Field::new(
353 key_field.name(),
354 item_field.data_type().clone(),
355 item_field.is_nullable() || key_field.is_nullable(),
356 ),
357 other_type => {
358 unreachable!(
359 "The first field in the schema must be a List or LargeList type. \
360 Found: {}. This should have been verified earlier in the code.",
361 other_type
362 )
363 }
364 };
365
366 let all_fields = vec![Arc::new(new_key_field)]
367 .into_iter()
368 .chain(remaining_fields)
369 .collect::<Vec<_>>();
370
371 Arc::new(Schema::new(Fields::from(all_fields)))
372}
373
374fn unnest_batch(
375 batch: arrow::record_batch::RecordBatch,
376 unnest_schema: SchemaRef,
377) -> datafusion_common::Result<RecordBatch> {
378 let mut columns_iter = batch.columns().iter().cloned();
379 let key_col = columns_iter.next().unwrap();
380 let remaining_cols = columns_iter.collect::<Vec<_>>();
381
382 let remaining_fields = unnest_schema
383 .fields
384 .iter()
385 .skip(1)
386 .cloned()
387 .collect::<Vec<_>>();
388
389 let remaining_batch = RecordBatch::try_new(
390 Arc::new(Schema::new(Fields::from(remaining_fields))),
391 remaining_cols,
392 )?;
393
394 let flatten_indices = extract_flatten_indices(key_col.as_ref());
395
396 let flattened_remaining =
397 arrow_select::take::take_record_batch(&remaining_batch, &flatten_indices)?;
398
399 let new_key_values = if let Some(key_list) = key_col.as_list_opt::<i32>() {
400 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
401 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
402 key_list
403 .values()
404 .slice(value_start, value_stop - value_start)
405 .clone()
406 } else if let Some(key_list) = key_col.as_list_opt::<i64>() {
407 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
408 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
409 key_list
410 .values()
411 .slice(value_start, value_stop - value_start)
412 .clone()
413 } else {
414 unreachable!("Should verify that the first column is a list earlier")
415 };
416
417 let all_columns = vec![new_key_values]
418 .into_iter()
419 .chain(flattened_remaining.columns().iter().cloned())
420 .collect::<Vec<_>>();
421
422 datafusion_common::Result::Ok(arrow::record_batch::RecordBatch::try_new(
423 unnest_schema,
424 all_columns,
425 )?)
426}
427
428fn unnest_chunks(
429 source: Pin<Box<dyn RecordBatchStream + Send>>,
430) -> Result<SendableRecordBatchStream> {
431 let unnest_schema = unnest_schema(source.schema().as_ref());
432 let unnest_schema_copy = unnest_schema.clone();
433 let source = source.try_filter_map(move |batch| {
434 std::future::ready(Some(unnest_batch(batch, unnest_schema.clone())).transpose())
435 });
436
437 Ok(Box::pin(RecordBatchStreamAdapter::new(
438 unnest_schema_copy,
439 source,
440 )))
441}
442
443async fn read_list_nulls(
444 store: Arc<dyn IndexStore>,
445 frag_reuse_index: Option<Arc<FragReuseIndex>>,
446) -> Result<RowAddrTreeMap> {
447 let reader = store.open_index_file(BITMAP_LOOKUP_NAME).await?;
448 if let Some(buffer_idx_str) = reader.schema().metadata.get(LABEL_LIST_NULLS_METADATA_KEY) {
449 let buffer_idx = buffer_idx_str.parse::<u32>().map_err(|err| {
450 Error::internal(format!(
451 "LabelList metadata key {} had invalid global buffer index {}: {}",
452 LABEL_LIST_NULLS_METADATA_KEY, buffer_idx_str, err
453 ))
454 })?;
455 let bytes = reader.read_global_buffer(buffer_idx).await?;
456 let null_map = RowAddrTreeMap::deserialize_from(bytes.as_ref())?;
457 return if let Some(frag_reuse_index) = frag_reuse_index {
458 Ok(frag_reuse_index.remap_row_addrs_tree_map(&null_map))
459 } else {
460 Ok(null_map)
461 };
462 }
463 Ok(RowAddrTreeMap::default())
464}
465
466fn serialize_list_nulls(null_map: &RowAddrTreeMap) -> Result<Bytes> {
467 let mut bytes = Vec::new();
468 null_map.serialize_into(&mut bytes)?;
469 Ok(Bytes::from(bytes))
470}
471
472async fn write_label_list_bitmap_index(
473 state: HashMap<ScalarValue, RowAddrTreeMap>,
474 store: &dyn IndexStore,
475 value_type: &DataType,
476 list_nulls: &RowAddrTreeMap,
477) -> Result<()> {
478 BitmapIndexPlugin::write_bitmap_index_with_extras(
479 state,
480 store,
481 value_type,
482 HashMap::new(),
483 vec![(
484 LABEL_LIST_NULLS_METADATA_KEY.to_string(),
485 serialize_list_nulls(list_nulls)?,
486 )],
487 )
488 .await
489}
490
491#[derive(Debug, Default)]
492pub struct LabelListIndexPlugin;
493
494#[async_trait]
495impl ScalarIndexPlugin for LabelListIndexPlugin {
496 fn name(&self) -> &str {
497 "LabelList"
498 }
499
500 fn new_training_request(
501 &self,
502 _params: &str,
503 field: &Field,
504 ) -> Result<Box<dyn TrainingRequest>> {
505 if !matches!(
506 field.data_type(),
507 DataType::List(_) | DataType::LargeList(_)
508 ) {
509 return Err(Error::invalid_input_source(format!(
510 "LabelList index can only be created on List or LargeList type columns. Column has type {:?}",
511 field.data_type()
512 )
513 .into()));
514 }
515
516 Ok(Box::new(DefaultTrainingRequest::new(
517 TrainingCriteria::new(TrainingOrdering::None).with_row_id(),
518 )))
519 }
520
521 fn provides_exact_answer(&self) -> bool {
522 true
523 }
524
525 fn version(&self) -> u32 {
526 LABEL_LIST_INDEX_VERSION
527 }
528
529 fn new_query_parser(
530 &self,
531 index_name: String,
532 _index_details: &prost_types::Any,
533 ) -> Option<Box<dyn ScalarQueryParser>> {
534 Some(Box::new(LabelListQueryParser::new(index_name)))
535 }
536
537 async fn train_index(
542 &self,
543 data: SendableRecordBatchStream,
544 index_store: &dyn IndexStore,
545 _request: Box<dyn TrainingRequest>,
546 fragment_ids: Option<Vec<u32>>,
547 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
548 ) -> Result<CreatedIndex> {
549 if fragment_ids.is_some() {
550 return Err(Error::invalid_input_source(
551 "LabelList index does not support fragment training".into(),
552 ));
553 }
554
555 let schema = data.schema();
556 let field = schema
557 .column_with_name(VALUE_COLUMN_NAME)
558 .ok_or_else(|| {
559 Error::invalid_input_source(
560 "Index training data missing value column"
561 .to_string()
562 .into(),
563 )
564 })?
565 .1;
566
567 if !matches!(
568 field.data_type(),
569 DataType::List(_) | DataType::LargeList(_)
570 ) {
571 return Err(Error::invalid_input_source(format!(
572 "LabelList index can only be created on List or LargeList type columns. Column has type {:?}",
573 field.data_type()
574 )
575 .into()));
576 }
577
578 let list_nulls = Arc::new(Mutex::new(RowAddrTreeMap::new()));
579 let data = track_list_nulls(data, list_nulls.clone());
580 let data = unnest_chunks(data)?;
581 let (state, value_type) =
582 BitmapIndexPlugin::build_bitmap_index_state(data, HashMap::new()).await?;
583 let list_nulls = list_nulls.lock().unwrap().clone();
584 write_label_list_bitmap_index(state, index_store, &value_type, &list_nulls).await?;
585 Ok(CreatedIndex {
586 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
587 .unwrap(),
588 index_version: LABEL_LIST_INDEX_VERSION,
589 files: Some(index_store.list_files_with_sizes().await?),
590 })
591 }
592
593 async fn load_index(
595 &self,
596 index_store: Arc<dyn IndexStore>,
597 _index_details: &prost_types::Any,
598 frag_reuse_index: Option<Arc<FragReuseIndex>>,
599 cache: &LanceCache,
600 ) -> Result<Arc<dyn ScalarIndex>> {
601 Ok(
602 LabelListIndex::load(index_store, frag_reuse_index, cache).await?
603 as Arc<dyn ScalarIndex>,
604 )
605 }
606}