1use lance_core::utils::row_addr_remap::RowAddrRemap;
5use std::{
6 any::Any,
7 collections::HashMap,
8 fmt::Debug,
9 pin::Pin,
10 sync::{Arc, Mutex},
11};
12
13use arrow::array::AsArray;
14use arrow_array::{Array, RecordBatch, UInt64Array};
15use arrow_schema::{DataType, Field, Fields, Schema, SchemaRef};
16use async_trait::async_trait;
17use bytes::Bytes;
18use datafusion::execution::RecordBatchStream;
19use datafusion::physical_plan::{SendableRecordBatchStream, stream::RecordBatchStreamAdapter};
20use datafusion_common::ScalarValue;
21use futures::{StreamExt, TryStream, TryStreamExt, stream::BoxStream};
22use lance_core::cache::{
23 CacheCodec, CacheCodecImpl, CacheEntryReader, CacheEntryWriter, CacheKey, CacheKeySchema,
24 KeyBuilder, LanceCache,
25};
26use lance_core::deepsize::DeepSizeOf;
27use lance_core::error::LanceOptionExt;
28use lance_core::{Error, ROW_ID, Result};
29use lance_select::{NullableRowAddrSet, RowAddrTreeMap, RowSetOps};
30use roaring::RoaringBitmap;
31use tracing::instrument;
32
33use super::{
34 AnyQuery, IndexFile, IndexStore, LabelListQuery, OldIndexDataFilter, ScalarIndex,
35 bitmap::BitmapIndex,
36};
37use super::{BuiltinIndexType, SargableQuery, ScalarIndexParams};
38use super::{MetricsCollector, SearchResult};
39use crate::pbold;
40use crate::scalar::bitmap::{BitmapIndexPlugin, BitmapIndexState};
41use crate::scalar::expression::{LabelListQueryParser, ScalarQueryParser};
42use crate::scalar::registry::{
43 BasicTrainer, DefaultTrainingRequest, ScalarIndexLoad, ScalarIndexPlugin, TrainingCriteria,
44 TrainingOrdering, TrainingRequest, VALUE_COLUMN_NAME, single_flight_open,
45};
46use crate::scalar::{CreatedIndex, RowIdRemapper, UpdateCriteria};
47use crate::{Index, IndexType};
48
49pub const BITMAP_LOOKUP_NAME: &str = "bitmap_page_lookup.lance";
50pub const LABEL_LIST_NULLS_METADATA_KEY: &str = "lance:label_list_nulls";
51pub const LABEL_LIST_NULLS_MIN_VERSION: i32 = 1;
52const LABEL_LIST_INDEX_VERSION: u32 = 1;
53
54#[async_trait]
55trait LabelListSubIndex: ScalarIndex + DeepSizeOf {
56 async fn search_exact(
57 &self,
58 query: &dyn AnyQuery,
59 metrics: &dyn MetricsCollector,
60 ) -> Result<NullableRowAddrSet> {
61 let result = self.search(query, metrics).await?;
62 match result {
63 SearchResult::Exact(row_ids) => {
64 Ok(row_ids.with_nulls(RowAddrTreeMap::new()))
68 }
69 _ => Err(Error::internal(
70 "Label list sub-index should return exact results".to_string(),
71 )),
72 }
73 }
74}
75
76impl<T: ScalarIndex + DeepSizeOf> LabelListSubIndex for T {}
77
78#[derive(Clone, Debug, DeepSizeOf)]
82pub struct LabelListIndex {
83 values_index: Arc<BitmapIndex>,
84 list_nulls: Arc<RowAddrTreeMap>,
85}
86
87impl LabelListIndex {
88 fn new(values_index: Arc<BitmapIndex>, list_nulls: Arc<RowAddrTreeMap>) -> Self {
89 Self {
90 values_index,
91 list_nulls,
92 }
93 }
94
95 async fn load(
96 store: Arc<dyn IndexStore>,
97 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
98 index_cache: &LanceCache,
99 ) -> Result<Arc<Self>> {
100 let values_index =
101 BitmapIndex::load(store.clone(), frag_reuse_index.clone(), index_cache).await?;
102 let list_nulls = read_list_nulls(store, frag_reuse_index).await?;
103 Ok(Arc::new(Self::new(values_index, Arc::new(list_nulls))))
104 }
105}
106
107#[async_trait]
108impl Index for LabelListIndex {
109 fn as_any(&self) -> &dyn Any {
110 self
111 }
112
113 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
114 self
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: &RowAddrRemap,
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.get(addr_as_u64).unwrap_or(Some(addr_as_u64))
226 }));
227 let file = write_label_list_bitmap_index(
228 remapped_state,
229 dest_store,
230 self.values_index.value_type(),
231 &remapped_nulls,
232 )
233 .await?;
234
235 Ok(CreatedIndex {
236 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
237 .unwrap(),
238 index_version: LABEL_LIST_INDEX_VERSION,
239 files: vec![file],
240 })
241 }
242
243 async fn update(
245 &self,
246 new_data: SendableRecordBatchStream,
247 dest_store: &dyn IndexStore,
248 old_data_filter: Option<super::OldIndexDataFilter>,
249 ) -> Result<CreatedIndex> {
250 let state = self.values_index.load_bitmap_index_state().await?;
251 let list_nulls = Arc::new(Mutex::new(RowAddrTreeMap::new()));
252 let new_data = track_list_nulls(new_data, list_nulls.clone());
253 let (merged_state, value_type) =
254 BitmapIndexPlugin::build_bitmap_index_state(unnest_chunks(new_data)?, state).await?;
255 let _ = old_data_filter;
256 let mut merged_nulls = (*self.list_nulls).clone();
257 let new_nulls = list_nulls.lock().unwrap().clone();
258 if !new_nulls.is_empty() {
259 merged_nulls |= &new_nulls;
260 }
261 let file =
262 write_label_list_bitmap_index(merged_state, dest_store, &value_type, &merged_nulls)
263 .await?;
264
265 Ok(CreatedIndex {
266 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
267 .unwrap(),
268 index_version: LABEL_LIST_INDEX_VERSION,
269 files: vec![file],
270 })
271 }
272
273 fn update_criteria(&self) -> UpdateCriteria {
274 UpdateCriteria::only_new_data(TrainingCriteria::new(TrainingOrdering::None).with_row_id())
275 }
276
277 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
278 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::LabelList))
279 }
280}
281
282fn extract_flatten_indices(list_arr: &dyn Array) -> UInt64Array {
283 if let Some(list_arr) = list_arr.as_list_opt::<i32>() {
284 let mut indices = Vec::with_capacity(list_arr.values().len());
285 let offsets = list_arr.value_offsets();
286 for (offset_idx, w) in offsets.windows(2).enumerate() {
287 let size = (w[1] - w[0]) as u64;
288 indices.extend((0..size).map(|_| offset_idx as u64));
289 }
290 UInt64Array::from(indices)
291 } else if let Some(list_arr) = list_arr.as_list_opt::<i64>() {
292 let mut indices = Vec::with_capacity(list_arr.values().len());
293 let offsets = list_arr.value_offsets();
294 for (offset_idx, w) in offsets.windows(2).enumerate() {
295 let size = (w[1] - w[0]) as u64;
296 indices.extend((0..size).map(|_| offset_idx as u64));
297 }
298 UInt64Array::from(indices)
299 } else {
300 unreachable!(
301 "Should verify that the first column is a list earlier. Got array of type: {}",
302 list_arr.data_type()
303 )
304 }
305}
306
307fn track_list_nulls(
309 source: SendableRecordBatchStream,
310 list_nulls: Arc<Mutex<RowAddrTreeMap>>,
311) -> SendableRecordBatchStream {
312 let schema = source.schema();
313 let stream = source.try_filter_map(move |batch| {
314 let list_nulls = list_nulls.clone();
315 async move {
316 record_list_nulls(&batch, &list_nulls)?;
317 Ok(Some(batch))
318 }
319 });
320
321 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
322}
323
324fn record_list_nulls(
325 batch: &RecordBatch,
326 list_nulls: &Arc<Mutex<RowAddrTreeMap>>,
327) -> datafusion_common::Result<()> {
328 let values = batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?;
329 let row_ids = batch.column_by_name(ROW_ID).expect_ok()?;
330 let row_ids = row_ids.as_any().downcast_ref::<UInt64Array>().unwrap();
331
332 let mut local_nulls = RowAddrTreeMap::new();
333 for i in 0..values.len() {
334 if values.is_null(i) {
335 local_nulls.insert(row_ids.value(i));
336 }
337 }
338 if !local_nulls.is_empty() {
339 let mut guard = list_nulls.lock().unwrap();
340 *guard |= &local_nulls;
341 }
342 Ok(())
343}
344
345fn unnest_schema(schema: &Schema) -> SchemaRef {
346 let mut fields_iter = schema.fields.iter().cloned();
347 let key_field = fields_iter.next().unwrap();
348 let remaining_fields = fields_iter.collect::<Vec<_>>();
349
350 let new_key_field = match key_field.data_type() {
351 DataType::List(item_field) | DataType::LargeList(item_field) => Field::new(
352 key_field.name(),
353 item_field.data_type().clone(),
354 item_field.is_nullable() || key_field.is_nullable(),
355 ),
356 other_type => {
357 unreachable!(
358 "The first field in the schema must be a List or LargeList type. \
359 Found: {}. This should have been verified earlier in the code.",
360 other_type
361 )
362 }
363 };
364
365 let all_fields = vec![Arc::new(new_key_field)]
366 .into_iter()
367 .chain(remaining_fields)
368 .collect::<Vec<_>>();
369
370 Arc::new(Schema::new(Fields::from(all_fields)))
371}
372
373fn unnest_batch(
374 batch: arrow::record_batch::RecordBatch,
375 unnest_schema: SchemaRef,
376) -> datafusion_common::Result<RecordBatch> {
377 let mut columns_iter = batch.columns().iter().cloned();
378 let key_col = columns_iter.next().unwrap();
379 let remaining_cols = columns_iter.collect::<Vec<_>>();
380
381 let remaining_fields = unnest_schema
382 .fields
383 .iter()
384 .skip(1)
385 .cloned()
386 .collect::<Vec<_>>();
387
388 let remaining_batch = RecordBatch::try_new(
389 Arc::new(Schema::new(Fields::from(remaining_fields))),
390 remaining_cols,
391 )?;
392
393 let flatten_indices = extract_flatten_indices(key_col.as_ref());
394
395 let flattened_remaining =
396 arrow_select::take::take_record_batch(&remaining_batch, &flatten_indices)?;
397
398 let new_key_values = if let Some(key_list) = key_col.as_list_opt::<i32>() {
399 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
400 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
401 key_list
402 .values()
403 .slice(value_start, value_stop - value_start)
404 .clone()
405 } else if let Some(key_list) = key_col.as_list_opt::<i64>() {
406 let value_start = key_list.value_offsets()[key_list.offset()] as usize;
407 let value_stop = key_list.value_offsets()[key_list.len()] as usize;
408 key_list
409 .values()
410 .slice(value_start, value_stop - value_start)
411 .clone()
412 } else {
413 unreachable!("Should verify that the first column is a list earlier")
414 };
415
416 let all_columns = vec![new_key_values]
417 .into_iter()
418 .chain(flattened_remaining.columns().iter().cloned())
419 .collect::<Vec<_>>();
420
421 datafusion_common::Result::Ok(arrow::record_batch::RecordBatch::try_new(
422 unnest_schema,
423 all_columns,
424 )?)
425}
426
427fn unnest_chunks(
428 source: Pin<Box<dyn RecordBatchStream + Send>>,
429) -> Result<SendableRecordBatchStream> {
430 let unnest_schema = unnest_schema(source.schema().as_ref());
431 let unnest_schema_copy = unnest_schema.clone();
432 let source = source.try_filter_map(move |batch| {
433 std::future::ready(Some(unnest_batch(batch, unnest_schema.clone())).transpose())
434 });
435
436 Ok(Box::pin(RecordBatchStreamAdapter::new(
437 unnest_schema_copy,
438 source,
439 )))
440}
441
442async fn read_list_nulls(
443 store: Arc<dyn IndexStore>,
444 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
445) -> Result<RowAddrTreeMap> {
446 let reader = store.open_index_file(BITMAP_LOOKUP_NAME).await?;
447 if let Some(buffer_idx_str) = reader.schema().metadata.get(LABEL_LIST_NULLS_METADATA_KEY) {
448 let buffer_idx = buffer_idx_str.parse::<u32>().map_err(|err| {
449 Error::internal(format!(
450 "LabelList metadata key {} had invalid global buffer index {}: {}",
451 LABEL_LIST_NULLS_METADATA_KEY, buffer_idx_str, err
452 ))
453 })?;
454 let bytes = reader.read_global_buffer(buffer_idx).await?;
455 let null_map = RowAddrTreeMap::deserialize_from(bytes.as_ref())?;
456 return if let Some(frag_reuse_index) = frag_reuse_index {
457 Ok(frag_reuse_index.remap_row_addrs_tree_map(&null_map))
458 } else {
459 Ok(null_map)
460 };
461 }
462 Ok(RowAddrTreeMap::default())
463}
464
465fn serialize_list_nulls(null_map: &RowAddrTreeMap) -> Result<Bytes> {
466 let mut bytes = Vec::new();
467 null_map.serialize_into(&mut bytes)?;
468 Ok(Bytes::from(bytes))
469}
470
471async fn write_label_list_bitmap_index(
472 state: HashMap<ScalarValue, RowAddrTreeMap>,
473 store: &dyn IndexStore,
474 value_type: &DataType,
475 list_nulls: &RowAddrTreeMap,
476) -> Result<IndexFile> {
477 BitmapIndexPlugin::write_bitmap_index_with_extras(
478 state,
479 store,
480 value_type,
481 HashMap::new(),
482 vec![(
483 LABEL_LIST_NULLS_METADATA_KEY.to_string(),
484 serialize_list_nulls(list_nulls)?,
485 )],
486 )
487 .await
488}
489
490pub async fn merge_label_list_indices(
500 source_indices: &[Arc<LabelListIndex>],
501 dest_store: &dyn IndexStore,
502 old_data_filter: Option<OldIndexDataFilter>,
503 progress: Arc<dyn crate::progress::IndexBuildProgress>,
504) -> Result<CreatedIndex> {
505 if source_indices.is_empty() {
506 return Err(Error::invalid_input(
507 "LabelList segment merge requires at least one source segment".to_string(),
508 ));
509 }
510
511 let value_type = source_indices[0].values_index.value_type().clone();
512 let mut merged_state = HashMap::<ScalarValue, RowAddrTreeMap>::new();
513 let mut merged_nulls = RowAddrTreeMap::new();
514
515 progress
516 .stage_start(
517 "merge_label_list_segments",
518 Some(source_indices.len() as u64),
519 "segments",
520 )
521 .await?;
522 for (idx, source_index) in source_indices.iter().enumerate() {
523 if source_index.values_index.value_type() != &value_type {
524 return Err(Error::invalid_input(format!(
525 "LabelList segment has value type {:?}, expected {:?}",
526 source_index.values_index.value_type(),
527 value_type
528 )));
529 }
530
531 let state = source_index.values_index.load_bitmap_index_state().await?;
532 for (key, mut bitmap) in state {
533 if let Some(filter) = old_data_filter.as_ref() {
534 filter.retain_old_rows(&mut bitmap);
535 }
536 if bitmap.is_empty() {
537 continue;
538 }
539 merged_state
540 .entry(key)
541 .and_modify(|existing| *existing |= &bitmap)
542 .or_insert(bitmap);
543 }
544 let mut list_nulls = source_index.list_nulls.as_ref().clone();
545 if let Some(filter) = old_data_filter.as_ref() {
546 filter.retain_old_rows(&mut list_nulls);
547 }
548 merged_nulls |= &list_nulls;
549 progress
550 .stage_progress("merge_label_list_segments", (idx + 1) as u64)
551 .await?;
552 }
553 progress.stage_complete("merge_label_list_segments").await?;
554
555 progress
556 .stage_start("write_label_list_index", Some(1), "files")
557 .await?;
558 let file =
559 write_label_list_bitmap_index(merged_state, dest_store, &value_type, &merged_nulls).await?;
560 progress.stage_progress("write_label_list_index", 1).await?;
561 progress.stage_complete("write_label_list_index").await?;
562
563 Ok(CreatedIndex {
564 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
565 .unwrap(),
566 index_version: LABEL_LIST_INDEX_VERSION,
567 files: vec![file],
568 })
569}
570
571#[derive(Debug, Clone)]
578pub struct LabelListIndexState {
579 bitmap_state: BitmapIndexState,
580 list_nulls: Arc<RowAddrTreeMap>,
581}
582
583impl DeepSizeOf for LabelListIndexState {
584 fn deep_size_of_children(&self, context: &mut lance_core::deepsize::Context) -> usize {
585 self.bitmap_state.deep_size_of_children(context)
586 + self.list_nulls.deep_size_of_children(context)
587 }
588}
589
590impl LabelListIndexState {
591 fn from_index(index: &LabelListIndex) -> Result<Self> {
592 Ok(Self {
593 bitmap_state: BitmapIndexState::from_index(&index.values_index)?,
594 list_nulls: index.list_nulls.clone(),
595 })
596 }
597
598 fn from_scalar_index(index: &dyn ScalarIndex) -> Result<Self> {
599 let label_list = index
600 .as_any()
601 .downcast_ref::<LabelListIndex>()
602 .ok_or_else(|| {
603 Error::internal(
604 "LabelListIndexState::from_scalar_index called with a non-label-list index",
605 )
606 })?;
607 Self::from_index(label_list)
608 }
609
610 fn into_label_list_index(
611 self,
612 store: Arc<dyn IndexStore>,
613 index_cache: &LanceCache,
614 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
615 ) -> Result<Arc<LabelListIndex>> {
616 let bitmap = self
617 .bitmap_state
618 .to_bitmap_index(store, index_cache, frag_reuse_index)?;
619 Ok(Arc::new(LabelListIndex::new(bitmap, self.list_nulls)))
620 }
621}
622
623impl CacheCodecImpl for LabelListIndexState {
624 const TYPE_ID: &'static str = "lance.scalar.LabelListIndexState";
625 const CURRENT_VERSION: u32 = 1;
626
627 fn serialize(&self, w: &mut CacheEntryWriter<'_>) -> Result<()> {
633 let mut nulls_bytes = Vec::with_capacity(self.list_nulls.serialized_size());
634 self.list_nulls.serialize_into(&mut nulls_bytes)?;
635 w.write_raw(&nulls_bytes)?;
636 self.bitmap_state.serialize(w)?;
638 Ok(())
639 }
640
641 fn deserialize(r: &mut CacheEntryReader<'_>) -> Result<Self> {
642 let nulls_bytes = r.read_raw()?;
643 let list_nulls = Arc::new(RowAddrTreeMap::deserialize_from(nulls_bytes.as_ref())?);
644 let bitmap_state = BitmapIndexState::deserialize(r)?;
648 Ok(Self {
649 bitmap_state,
650 list_nulls,
651 })
652 }
653}
654
655struct LabelListIndexStateKey;
656
657impl CacheKey for LabelListIndexStateKey {
658 type ValueType = LabelListIndexState;
659
660 fn key(&self) -> std::borrow::Cow<'_, str> {
661 "state".into()
662 }
663
664 fn type_name() -> &'static str {
665 "LabelListIndexState"
666 }
667
668 fn schema() -> CacheKeySchema {
669 CacheKeySchema::new("lance.scalar.label-list-index-state-key", 1)
670 }
671
672 fn write_key(&self, builder: &mut KeyBuilder) {
673 builder.write_variant(0);
674 }
675
676 fn codec() -> Option<CacheCodec> {
677 Some(CacheCodec::from_impl::<LabelListIndexState>())
678 }
679}
680
681#[derive(Debug, Default)]
682pub struct LabelListIndexPlugin;
683
684pub(super) fn validate_label_list_data_type(data_type: &DataType) -> Result<()> {
685 let item_type = match data_type {
686 DataType::List(item_field) | DataType::LargeList(item_field) => item_field.data_type(),
687 _ => {
688 return Err(Error::invalid_input_source(
689 format!(
690 "LabelList index can only be created on List or LargeList type columns. Column has type {:?}",
691 data_type
692 )
693 .into(),
694 ));
695 }
696 };
697
698 if item_type.is_nested() {
699 return Err(Error::invalid_input_source(
700 format!(
701 "LabelList index item type must be non-nested. Column has type {:?}",
702 data_type
703 )
704 .into(),
705 ));
706 }
707
708 Ok(())
709}
710
711#[async_trait]
712impl BasicTrainer for LabelListIndexPlugin {
713 fn new_training_request(
714 &self,
715 _params: &str,
716 field: &Field,
717 ) -> Result<Box<dyn TrainingRequest>> {
718 validate_label_list_data_type(field.data_type())?;
719
720 Ok(Box::new(DefaultTrainingRequest::new(
721 TrainingCriteria::new(TrainingOrdering::None).with_row_id(),
722 )))
723 }
724
725 async fn train_index(
730 &self,
731 data: SendableRecordBatchStream,
732 index_store: &dyn IndexStore,
733 _request: Box<dyn TrainingRequest>,
734 _fragment_ids: Option<Vec<u32>>,
739 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
740 ) -> Result<CreatedIndex> {
741 let schema = data.schema();
742 let field = schema
743 .column_with_name(VALUE_COLUMN_NAME)
744 .ok_or_else(|| {
745 Error::invalid_input_source(
746 "Index training data missing value column"
747 .to_string()
748 .into(),
749 )
750 })?
751 .1;
752
753 validate_label_list_data_type(field.data_type())?;
754
755 let list_nulls = Arc::new(Mutex::new(RowAddrTreeMap::new()));
756 let data = track_list_nulls(data, list_nulls.clone());
757 let data = unnest_chunks(data)?;
758 let (state, value_type) =
759 BitmapIndexPlugin::build_bitmap_index_state(data, HashMap::new()).await?;
760 let list_nulls = list_nulls.lock().unwrap().clone();
761 let file =
762 write_label_list_bitmap_index(state, index_store, &value_type, &list_nulls).await?;
763 Ok(CreatedIndex {
764 index_details: prost_types::Any::from_msg(&pbold::LabelListIndexDetails::default())
765 .unwrap(),
766 index_version: LABEL_LIST_INDEX_VERSION,
767 files: vec![file],
768 })
769 }
770}
771
772#[async_trait]
773impl ScalarIndexPlugin for LabelListIndexPlugin {
774 fn basic_trainer(&self) -> Option<&dyn BasicTrainer> {
775 Some(self)
776 }
777
778 fn name(&self) -> &str {
779 "LabelList"
780 }
781
782 fn provides_exact_answer(&self) -> bool {
783 true
784 }
785
786 fn version(&self) -> u32 {
787 LABEL_LIST_INDEX_VERSION
788 }
789
790 fn new_query_parser(
791 &self,
792 index_name: String,
793 _index_details: &prost_types::Any,
794 ) -> Option<Box<dyn ScalarQueryParser>> {
795 Some(Box::new(LabelListQueryParser::new(
796 index_name,
797 self.name().to_string(),
798 )))
799 }
800
801 async fn load_index(
803 &self,
804 index_store: Arc<dyn IndexStore>,
805 _index_details: &prost_types::Any,
806 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
807 cache: &LanceCache,
808 ) -> Result<Arc<dyn ScalarIndex>> {
809 Ok(
810 LabelListIndex::load(index_store, frag_reuse_index, cache).await?
811 as Arc<dyn ScalarIndex>,
812 )
813 }
814
815 async fn get_from_cache(
816 &self,
817 index_store: Arc<dyn IndexStore>,
818 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
819 cache: &LanceCache,
820 ) -> Result<Option<Arc<dyn ScalarIndex>>> {
821 let Some(state) = cache.get_with_key(&LabelListIndexStateKey).await else {
822 return Ok(None);
823 };
824 let state = (*state).clone();
825 let index = state.into_label_list_index(index_store, cache, frag_reuse_index)?;
826 Ok(Some(index as Arc<dyn ScalarIndex>))
827 }
828
829 async fn put_in_cache(&self, cache: &LanceCache, index: Arc<dyn ScalarIndex>) -> Result<()> {
830 let state = LabelListIndexState::from_scalar_index(index.as_ref())?;
831 cache
832 .insert_with_key(&LabelListIndexStateKey, Arc::new(state))
833 .await;
834 Ok(())
835 }
836
837 async fn get_or_insert_in_cache(
838 &self,
839 index_store: Arc<dyn IndexStore>,
840 frag_reuse_index: Option<Arc<dyn RowIdRemapper>>,
841 cache: &LanceCache,
842 load: ScalarIndexLoad<'_>,
843 ) -> Result<Arc<dyn ScalarIndex>> {
844 single_flight_open(
845 cache,
846 LabelListIndexStateKey,
847 load,
848 LabelListIndexState::from_scalar_index,
849 move |state| {
850 Ok((*state)
851 .clone()
852 .into_label_list_index(index_store, cache, frag_reuse_index)?
853 as Arc<dyn ScalarIndex>)
854 },
855 )
856 .await
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use std::collections::BTreeMap;
863
864 use datafusion_common::ScalarValue;
865 use lance_core::cache::CacheCodec;
866 use lance_core::utils::address::RowAddress;
867 use rstest::rstest;
868
869 use super::super::bitmap::BitmapIndexState;
870 use super::super::btree::OrderableScalarValue;
871 use super::*;
872
873 #[rstest]
874 #[case::list(DataType::List(Arc::new(Field::new(
875 "item",
876 DataType::List(Arc::new(Field::new("item", DataType::Int64, true))),
877 true,
878 ))))]
879 #[case::large_list(DataType::LargeList(Arc::new(Field::new(
880 "item",
881 DataType::List(Arc::new(Field::new("item", DataType::Int64, true))),
882 true,
883 ))))]
884 fn test_rejects_nested_item_type(#[case] data_type: DataType) {
885 let field = Field::new(VALUE_COLUMN_NAME, data_type, true);
886 let error = LabelListIndexPlugin
887 .new_training_request("", &field)
888 .err()
889 .expect("nested item type should be rejected");
890
891 assert!(
892 matches!(error, Error::InvalidInput { .. }),
893 "expected invalid input error, got: {error}"
894 );
895 assert!(
896 error
897 .to_string()
898 .contains("LabelList index item type must be non-nested"),
899 "unexpected error: {error}"
900 );
901 }
902
903 fn sample_state() -> LabelListIndexState {
904 let mut index_map = BTreeMap::new();
905 for k in 0..32i32 {
906 index_map.insert(
907 OrderableScalarValue(ScalarValue::Int32(Some(k))),
908 k as usize,
909 );
910 }
911 let mut bitmap_nulls = RowAddrTreeMap::new();
912 bitmap_nulls.insert(RowAddress::new_from_parts(0, 3).into());
913 let bitmap_state =
914 BitmapIndexState::new_for_test(index_map, bitmap_nulls, DataType::Int32).unwrap();
915
916 let mut list_nulls = RowAddrTreeMap::new();
917 list_nulls.insert(RowAddress::new_from_parts(0, 9).into());
918 LabelListIndexState {
919 bitmap_state,
920 list_nulls: Arc::new(list_nulls),
921 }
922 }
923
924 #[test]
925 fn test_label_list_state_codec_roundtrip() {
926 let state = sample_state();
927 let mut buf = Vec::new();
928 state
929 .serialize(&mut CacheEntryWriter::new(&mut buf))
930 .unwrap();
931 let data = Bytes::from(buf);
932 let mut reader = CacheEntryReader::new(&data, 0, LabelListIndexState::CURRENT_VERSION);
933 let restored = LabelListIndexState::deserialize(&mut reader).unwrap();
934
935 assert_eq!(&*restored.list_nulls, &*state.list_nulls);
936 assert_eq!(
937 restored.bitmap_state.lookup_batch(),
938 state.bitmap_state.lookup_batch()
939 );
940 assert_eq!(
941 restored.bitmap_state.null_map(),
942 state.bitmap_state.null_map()
943 );
944 }
945
946 #[test]
950 fn test_label_list_nested_lookup_is_zero_copy() {
951 const ALIGN: usize = 64;
952 let codec = CacheCodec::from_impl::<LabelListIndexState>();
953 let any: Arc<dyn std::any::Any + Send + Sync> = Arc::new(sample_state());
954 let mut buf = Vec::new();
955 codec.serialize(&any, &mut buf).unwrap();
956
957 let mut v = vec![0u8; buf.len() + ALIGN];
958 let pad = (ALIGN - (v.as_ptr() as usize % ALIGN)) % ALIGN;
959 v[pad..pad + buf.len()].copy_from_slice(&buf);
960 let data = Bytes::from(v).slice(pad..pad + buf.len());
961
962 let restored = codec.deserialize(&data).hit().unwrap();
963 let restored = restored.downcast::<LabelListIndexState>().unwrap();
964
965 let base = data.as_ptr() as usize;
966 let end = base + data.len();
967 for col in restored.bitmap_state.lookup_batch().columns() {
968 for buffer in col.to_data().buffers() {
969 let ptr = buffer.as_ptr() as usize;
970 assert!(
971 ptr >= base && ptr < end,
972 "nested bitmap lookup buffer was realigned — misaligned IPC section",
973 );
974 }
975 }
976 }
977}