1use crate::Any;
16use crate::pbold;
17use crate::scalar::expression::{SargableQueryParser, ScalarQueryParser};
18use crate::scalar::registry::{
19 ScalarIndexPlugin, TrainingCriteria, TrainingOrdering, TrainingRequest,
20};
21use crate::scalar::{
22 BuiltinIndexType, CreatedIndex, SargableQuery, ScalarIndexParams, UpdateCriteria,
23 compute_next_prefix,
24};
25use datafusion::functions_aggregate::min_max::{MaxAccumulator, MinAccumulator};
26use datafusion_expr::Accumulator;
27use lance_core::cache::{LanceCache, WeakLanceCache};
28use serde::{Deserialize, Serialize};
29use std::sync::LazyLock;
30
31use arrow_array::{ArrayRef, RecordBatch, UInt32Array, UInt64Array, new_empty_array};
32use arrow_schema::{DataType, Field};
33use datafusion::execution::SendableRecordBatchStream;
34use datafusion_common::ScalarValue;
35use std::{collections::HashMap, sync::Arc};
36
37use super::{AnyQuery, IndexStore, MetricsCollector, ScalarIndex, SearchResult};
38use crate::scalar::FragReuseIndex;
39use crate::vector::VectorIndex;
40use crate::{Index, IndexType};
41use async_trait::async_trait;
42use deepsize::DeepSizeOf;
43use lance_core::Error;
44use lance_core::Result;
45use roaring::RoaringBitmap;
46
47use super::zoned::{ZoneBound, ZoneProcessor, ZoneTrainer, rebuild_zones, search_zones};
48const ROWS_PER_ZONE_DEFAULT: u64 = 8192; const ZONEMAP_FILENAME: &str = "zonemap.lance";
51const ZONEMAP_SIZE_META_KEY: &str = "rows_per_zone";
52const ZONEMAP_INDEX_VERSION: u32 = 0;
53
54#[derive(Debug, PartialEq, Clone)]
56struct ZoneMapStatistics {
57 min: ScalarValue,
58 max: ScalarValue,
59 null_count: u32,
60 nan_count: u32,
62 bound: ZoneBound,
65}
66
67impl DeepSizeOf for ZoneMapStatistics {
68 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
69 let min_size = self.min.size() - std::mem::size_of::<ScalarValue>();
71 let max_size = self.max.size() - std::mem::size_of::<ScalarValue>();
72
73 min_size + max_size
74 }
75}
76
77impl AsRef<ZoneBound> for ZoneMapStatistics {
78 fn as_ref(&self) -> &ZoneBound {
79 &self.bound
80 }
81}
82
83pub struct ZoneMapIndex {
105 zones: Vec<ZoneMapStatistics>,
106 data_type: DataType,
107 rows_per_zone: u64,
109 store: Arc<dyn IndexStore>,
110 fri: Option<Arc<FragReuseIndex>>,
111 index_cache: WeakLanceCache,
112}
113
114impl std::fmt::Debug for ZoneMapIndex {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("ZoneMapIndex")
117 .field("zones", &self.zones)
118 .field("data_type", &self.data_type)
119 .field("rows_per_zone", &self.rows_per_zone)
120 .field("store", &self.store)
121 .field("fri", &self.fri)
122 .field("index_cache", &self.index_cache)
123 .finish()
124 }
125}
126
127impl DeepSizeOf for ZoneMapIndex {
128 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
129 self.zones.deep_size_of_children(context)
130 }
131}
132
133impl ZoneMapIndex {
134 fn evaluate_zone_against_query(
138 &self,
139 zone: &ZoneMapStatistics,
140 query: &SargableQuery,
141 ) -> Result<bool> {
142 use std::ops::Bound;
143
144 match query {
145 SargableQuery::IsNull() => {
146 Ok(zone.null_count > 0)
148 }
149 SargableQuery::Equals(target) => {
150 if target.is_null() {
153 return Ok(zone.null_count > 0);
154 }
155
156 let is_nan = match target {
158 ScalarValue::Float16(Some(f)) => f.is_nan(),
159 ScalarValue::Float32(Some(f)) => f.is_nan(),
160 ScalarValue::Float64(Some(f)) => f.is_nan(),
161 _ => false,
162 };
163
164 if is_nan {
165 return Ok(zone.nan_count > 0);
166 }
167
168 let min_check = target >= &zone.min;
171 let max_check = match &zone.max {
172 ScalarValue::Float16(Some(f)) if f.is_nan() => true,
173 ScalarValue::Float32(Some(f)) if f.is_nan() => true,
174 ScalarValue::Float64(Some(f)) if f.is_nan() => true,
175 _ => target <= &zone.max,
176 };
177 Ok(min_check && max_check)
178 }
179 SargableQuery::Range(start, end) => {
180 let zone_min = &zone.min;
183 let zone_max = &zone.max;
184
185 let start_check = match start {
186 Bound::Unbounded => true,
187 Bound::Included(s) => {
188 match s {
190 ScalarValue::Float16(Some(f)) => {
191 if f.is_nan() {
192 return Ok(zone.nan_count > 0);
193 }
194 }
195 ScalarValue::Float32(Some(f)) => {
196 if f.is_nan() {
197 return Ok(zone.nan_count > 0);
198 }
199 }
200 ScalarValue::Float64(Some(f)) => {
201 if f.is_nan() {
202 return Ok(zone.nan_count > 0);
203 }
204 }
205 _ => {}
206 }
207 match zone_max {
211 ScalarValue::Float16(Some(f)) if f.is_nan() => true,
212 ScalarValue::Float32(Some(f)) if f.is_nan() => true,
213 ScalarValue::Float64(Some(f)) if f.is_nan() => true,
214 _ => zone_max >= s,
215 }
216 }
217 Bound::Excluded(s) => {
218 match s {
220 ScalarValue::Float16(Some(f)) => {
221 if f.is_nan() {
222 return Ok(false); }
224 }
225 ScalarValue::Float32(Some(f)) => {
226 if f.is_nan() {
227 return Ok(false); }
229 }
230 ScalarValue::Float64(Some(f)) => {
231 if f.is_nan() {
232 return Ok(false); }
234 }
235 _ => {}
236 }
237 zone_max > s
238 }
239 };
240
241 let end_check = match end {
242 Bound::Unbounded => true,
243 Bound::Included(e) => {
244 match e {
246 ScalarValue::Float16(Some(f)) => {
247 if f.is_nan() {
248 return Ok(zone.nan_count > 0 || zone_min <= e);
250 }
251 }
252 ScalarValue::Float32(Some(f)) => {
253 if f.is_nan() {
254 return Ok(zone.nan_count > 0 || zone_min <= e);
255 }
256 }
257 ScalarValue::Float64(Some(f)) => {
258 if f.is_nan() {
259 return Ok(zone.nan_count > 0 || zone_min <= e);
260 }
261 }
262 _ => {}
263 }
264 zone_min <= e
265 }
266 Bound::Excluded(e) => {
267 match e {
269 ScalarValue::Float16(Some(f)) => {
270 if f.is_nan() {
271 return Ok(true);
273 }
274 }
275 ScalarValue::Float32(Some(f)) => {
276 if f.is_nan() {
277 return Ok(true);
278 }
279 }
280 ScalarValue::Float64(Some(f)) => {
281 if f.is_nan() {
282 return Ok(true);
283 }
284 }
285 _ => {}
286 }
287 zone_min < e
288 }
289 };
290
291 Ok(start_check && end_check)
292 }
293 SargableQuery::IsIn(values) => {
294 Ok(values.iter().any(|value| {
296 if value.is_null() {
297 zone.null_count > 0
298 } else {
299 match value {
300 ScalarValue::Float16(Some(f)) => {
301 if f.is_nan() {
302 zone.nan_count > 0
303 } else {
304 value >= &zone.min && value <= &zone.max
305 }
306 }
307 ScalarValue::Float32(Some(f)) => {
308 if f.is_nan() {
309 zone.nan_count > 0
310 } else {
311 value >= &zone.min && value <= &zone.max
312 }
313 }
314 ScalarValue::Float64(Some(f)) => {
315 if f.is_nan() {
316 zone.nan_count > 0
317 } else {
318 value >= &zone.min && value <= &zone.max
319 }
320 }
321 _ => value >= &zone.min && value <= &zone.max,
322 }
323 }
324 }))
325 }
326 SargableQuery::FullTextSearch(_) => Err(Error::not_supported_source(
327 "full text search is not supported for zonemap indexes".into(),
328 )),
329 SargableQuery::LikePrefix(prefix) => {
330 let prefix_str = match prefix {
340 ScalarValue::Utf8(Some(s)) => s.as_str(),
341 ScalarValue::LargeUtf8(Some(s)) => s.as_str(),
342 _ => return Ok(true), };
344
345 if prefix_str.is_empty() {
347 return Ok(true);
348 }
349
350 let max_check = &zone.max >= prefix;
352 if !max_check {
353 return Ok(false);
354 }
355
356 let next_prefix = compute_next_prefix(prefix_str);
359
360 match next_prefix {
361 Some(next) => {
362 let next_scalar = match prefix {
364 ScalarValue::Utf8(_) => ScalarValue::Utf8(Some(next)),
365 ScalarValue::LargeUtf8(_) => ScalarValue::LargeUtf8(Some(next)),
366 _ => return Ok(true),
367 };
368 Ok(zone.min < next_scalar)
369 }
370 None => {
371 Ok(true)
373 }
374 }
375 }
376 }
377 }
378
379 async fn load(
381 store: Arc<dyn IndexStore>,
382 fri: Option<Arc<FragReuseIndex>>,
383 index_cache: &LanceCache,
384 ) -> Result<Arc<Self>>
385 where
386 Self: Sized,
387 {
388 let index_file = store.open_index_file(ZONEMAP_FILENAME).await?;
389 let zone_maps = index_file
390 .read_range(0..index_file.num_rows(), None)
391 .await?;
392 let file_schema = index_file.schema();
393
394 let rows_per_zone: u64 = file_schema
395 .metadata
396 .get(ZONEMAP_SIZE_META_KEY)
397 .and_then(|bs| bs.parse().ok())
398 .unwrap_or(ROWS_PER_ZONE_DEFAULT);
399 Ok(Arc::new(Self::try_from_serialized(
400 zone_maps,
401 store,
402 fri,
403 index_cache,
404 rows_per_zone,
405 )?))
406 }
407
408 fn try_from_serialized(
409 data: RecordBatch,
410 store: Arc<dyn IndexStore>,
411 fri: Option<Arc<FragReuseIndex>>,
412 index_cache: &LanceCache,
413 rows_per_zone: u64,
414 ) -> Result<Self> {
415 let min_col = data
417 .column_by_name("min")
418 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'min' column"))?;
419 let max_col = data
420 .column_by_name("max")
421 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'max' column"))?;
422 let null_count_col = data
423 .column_by_name("null_count")
424 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'null_count' column"))?
425 .as_any()
426 .downcast_ref::<arrow_array::UInt32Array>()
427 .ok_or_else(|| {
428 Error::invalid_input("ZoneMapIndex: 'null_count' column is not UInt32")
429 })?;
430 let nan_count_col = data
431 .column_by_name("nan_count")
432 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'nan_count' column"))?
433 .as_any()
434 .downcast_ref::<arrow_array::UInt32Array>()
435 .ok_or_else(|| {
436 Error::invalid_input("ZoneMapIndex: 'nan_count' column is not UInt32")
437 })?;
438 let zone_length = data
439 .column_by_name("zone_length")
440 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'zone_length' column"))?
441 .as_any()
442 .downcast_ref::<arrow_array::UInt64Array>()
443 .ok_or_else(|| {
444 Error::invalid_input("ZoneMapIndex: 'zone_length' column is not UInt64")
445 })?;
446
447 let fragment_id_col = data
448 .column_by_name("fragment_id")
449 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'fragment_id' column"))?
450 .as_any()
451 .downcast_ref::<arrow_array::UInt64Array>()
452 .ok_or_else(|| {
453 Error::invalid_input("ZoneMapIndex: 'fragment_id' column is not UInt64")
454 })?;
455
456 let zone_start_col = data
457 .column_by_name("zone_start")
458 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'zone_start' column"))?
459 .as_any()
460 .downcast_ref::<arrow_array::UInt64Array>()
461 .ok_or_else(|| {
462 Error::invalid_input("ZoneMapIndex: 'zone_start' column is not UInt64")
463 })?;
464
465 let data_type = min_col.data_type().clone();
466
467 if data.num_rows() == 0 {
468 return Ok(Self {
469 zones: Vec::new(),
470 data_type,
471 rows_per_zone,
472 store,
473 fri,
474 index_cache: WeakLanceCache::from(index_cache),
475 });
476 }
477
478 let num_zones = data.num_rows();
479 let mut zones = Vec::with_capacity(num_zones);
480
481 for i in 0..num_zones {
482 let min = ScalarValue::try_from_array(min_col, i)?;
483 let max = ScalarValue::try_from_array(max_col, i)?;
484 let null_count = null_count_col.value(i);
485 let nan_count = nan_count_col.value(i);
486 zones.push(ZoneMapStatistics {
487 min,
488 max,
489 null_count,
490 nan_count,
491 bound: ZoneBound {
492 fragment_id: fragment_id_col.value(i),
493 start: zone_start_col.value(i),
494 length: zone_length.value(i) as usize,
495 },
496 });
497 }
498
499 Ok(Self {
500 zones,
501 data_type,
502 rows_per_zone,
503 store,
504 fri,
505 index_cache: WeakLanceCache::from(index_cache),
506 })
507 }
508}
509
510#[async_trait]
511impl Index for ZoneMapIndex {
512 fn as_any(&self) -> &dyn Any {
513 self
514 }
515
516 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
517 self
518 }
519
520 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn VectorIndex>> {
521 Err(Error::invalid_input_source(
522 "ZoneMapIndex is not a vector index".into(),
523 ))
524 }
525
526 async fn prewarm(&self) -> Result<()> {
527 Ok(())
529 }
530
531 fn statistics(&self) -> Result<serde_json::Value> {
532 Ok(serde_json::json!({
533 "num_zones": self.zones.len(),
534 "rows_per_zone": self.rows_per_zone,
535 }))
536 }
537
538 fn index_type(&self) -> IndexType {
539 IndexType::ZoneMap
540 }
541
542 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
543 let mut frag_ids = RoaringBitmap::new();
544
545 for zone in &self.zones {
547 frag_ids.insert(zone.bound.fragment_id as u32);
548 }
549
550 Ok(frag_ids)
551 }
552}
553
554#[async_trait]
555impl ScalarIndex for ZoneMapIndex {
556 async fn search(
557 &self,
558 query: &dyn AnyQuery,
559 metrics: &dyn MetricsCollector,
560 ) -> Result<SearchResult> {
561 let query = query.as_any().downcast_ref::<SargableQuery>().unwrap();
562 search_zones(&self.zones, metrics, |zone| {
563 self.evaluate_zone_against_query(zone, query)
564 })
565 }
566
567 fn can_remap(&self) -> bool {
568 false
569 }
570
571 async fn remap(
573 &self,
574 _mapping: &HashMap<u64, Option<u64>>,
575 _dest_store: &dyn IndexStore,
576 ) -> Result<CreatedIndex> {
577 Err(Error::invalid_input_source(
578 "ZoneMapIndex does not support remap".into(),
579 ))
580 }
581
582 async fn update(
584 &self,
585 new_data: SendableRecordBatchStream,
586 dest_store: &dyn IndexStore,
587 _old_data_filter: Option<super::OldIndexDataFilter>,
588 ) -> Result<CreatedIndex> {
589 let schema = new_data.schema();
591 let value_type = schema.field(0).data_type().clone();
592
593 let options = ZoneMapIndexBuilderParams::new(self.rows_per_zone);
594 let processor = ZoneMapProcessor::new(value_type.clone())?;
595 let trainer = ZoneTrainer::new(processor, self.rows_per_zone)?;
596 let updated_zones = rebuild_zones(&self.zones, trainer, new_data).await?;
597
598 let mut builder = ZoneMapIndexBuilder::try_new(options, self.data_type.clone())?;
600 builder.options.rows_per_zone = self.rows_per_zone;
601 builder.maps = updated_zones;
602 builder.write_index(dest_store).await?;
603
604 Ok(CreatedIndex {
605 index_details: prost_types::Any::from_msg(&pbold::ZoneMapIndexDetails::default())
606 .unwrap(),
607 index_version: ZONEMAP_INDEX_VERSION,
608 files: Some(dest_store.list_files_with_sizes().await?),
609 })
610 }
611
612 fn update_criteria(&self) -> UpdateCriteria {
613 UpdateCriteria::only_new_data(
614 TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
615 )
616 }
617
618 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
619 let params = serde_json::to_value(ZoneMapIndexBuilderParams::new(self.rows_per_zone))?;
620 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::ZoneMap).with_params(¶ms))
621 }
622}
623
624fn default_rows_per_zone() -> u64 {
625 *DEFAULT_ROWS_PER_ZONE
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize)]
629pub struct ZoneMapIndexBuilderParams {
630 #[serde(default = "default_rows_per_zone")]
631 rows_per_zone: u64,
632}
633
634static DEFAULT_ROWS_PER_ZONE: LazyLock<u64> = LazyLock::new(|| {
635 std::env::var("LANCE_ZONEMAP_DEFAULT_ROWS_PER_ZONE")
636 .unwrap_or_else(|_| (ROWS_PER_ZONE_DEFAULT).to_string())
637 .parse()
638 .expect("failed to parse LANCE_ZONEMAP_DEFAULT_ROWS_PER_ZONE")
639});
640
641impl Default for ZoneMapIndexBuilderParams {
642 fn default() -> Self {
643 Self {
644 rows_per_zone: *DEFAULT_ROWS_PER_ZONE,
645 }
646 }
647}
648
649impl ZoneMapIndexBuilderParams {
650 pub fn new(rows_per_zone: u64) -> Self {
651 Self { rows_per_zone }
652 }
653
654 pub fn rows_per_zone(&self) -> u64 {
655 self.rows_per_zone
656 }
657}
658
659pub struct ZoneMapIndexBuilder {
661 options: ZoneMapIndexBuilderParams,
662
663 items_type: DataType,
664 maps: Vec<ZoneMapStatistics>,
665}
666
667impl ZoneMapIndexBuilder {
668 pub fn try_new(options: ZoneMapIndexBuilderParams, items_type: DataType) -> Result<Self> {
669 Ok(Self {
670 options,
671 items_type,
672 maps: Vec::new(),
673 })
674 }
675
676 pub async fn train(&mut self, batches_source: SendableRecordBatchStream) -> Result<()> {
680 let processor = ZoneMapProcessor::new(self.items_type.clone())?;
681 let trainer = ZoneTrainer::new(processor, self.options.rows_per_zone)?;
682 self.maps = trainer.train(batches_source).await?;
683 Ok(())
684 }
685
686 fn zonemap_stats_as_batch(&self) -> Result<RecordBatch> {
687 let mins = if self.maps.is_empty() {
689 new_empty_array(&self.items_type)
690 } else {
691 ScalarValue::iter_to_array(self.maps.iter().map(|stat| stat.min.clone()))?
692 };
693 let maxs = if self.maps.is_empty() {
694 new_empty_array(&self.items_type)
695 } else {
696 ScalarValue::iter_to_array(self.maps.iter().map(|stat| stat.max.clone()))?
697 };
698 let null_counts =
699 UInt32Array::from_iter_values(self.maps.iter().map(|stat| stat.null_count));
700
701 let nan_counts = UInt32Array::from_iter_values(self.maps.iter().map(|stat| stat.nan_count));
702
703 let fragment_ids =
704 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.fragment_id));
705
706 let zone_lengths =
707 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.length as u64));
708
709 let zone_starts =
710 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.start));
711
712 let schema = Arc::new(arrow_schema::Schema::new(vec![
713 Field::new("min", self.items_type.clone(), true),
715 Field::new("max", self.items_type.clone(), true),
716 Field::new("null_count", DataType::UInt32, false),
717 Field::new("nan_count", DataType::UInt32, false),
718 Field::new("fragment_id", DataType::UInt64, false),
719 Field::new("zone_start", DataType::UInt64, false),
720 Field::new("zone_length", DataType::UInt64, false),
721 ]));
722
723 let columns: Vec<ArrayRef> = vec![
724 mins,
725 maxs,
726 Arc::new(null_counts) as ArrayRef,
727 Arc::new(nan_counts) as ArrayRef,
728 Arc::new(fragment_ids) as ArrayRef,
729 Arc::new(zone_starts) as ArrayRef,
730 Arc::new(zone_lengths) as ArrayRef,
731 ];
732 Ok(RecordBatch::try_new(schema, columns)?)
733 }
734
735 pub async fn write_index(self, index_store: &dyn IndexStore) -> Result<()> {
736 let record_batch = self.zonemap_stats_as_batch()?;
737
738 let mut file_schema = record_batch.schema().as_ref().clone();
739 file_schema.metadata.insert(
740 ZONEMAP_SIZE_META_KEY.to_string(),
741 self.options.rows_per_zone.to_string(),
742 );
743
744 let mut index_file = index_store
745 .new_index_file(ZONEMAP_FILENAME, Arc::new(file_schema))
746 .await?;
747 index_file.write_record_batch(record_batch).await?;
748 index_file.finish().await?;
749 Ok(())
750 }
751}
752
753struct ZoneMapProcessor {
756 data_type: DataType,
757 min: MinAccumulator,
758 max: MaxAccumulator,
759 null_count: u32,
760 nan_count: u32,
761}
762
763impl ZoneMapProcessor {
764 fn new(data_type: DataType) -> Result<Self> {
765 let min = MinAccumulator::try_new(&data_type)?;
766 let max = MaxAccumulator::try_new(&data_type)?;
767 Ok(Self {
768 data_type,
769 min,
770 max,
771 null_count: 0,
772 nan_count: 0,
773 })
774 }
775
776 fn count_nans(array: &ArrayRef) -> u32 {
777 match array.data_type() {
778 DataType::Float16 => {
779 let array = array
780 .as_any()
781 .downcast_ref::<arrow_array::Float16Array>()
782 .unwrap();
783 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
784 }
785 DataType::Float32 => {
786 let array = array
787 .as_any()
788 .downcast_ref::<arrow_array::Float32Array>()
789 .unwrap();
790 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
791 }
792 DataType::Float64 => {
793 let array = array
794 .as_any()
795 .downcast_ref::<arrow_array::Float64Array>()
796 .unwrap();
797 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
798 }
799 _ => 0,
800 }
801 }
802}
803
804impl ZoneProcessor for ZoneMapProcessor {
805 type ZoneStatistics = ZoneMapStatistics;
806
807 fn process_chunk(&mut self, array: &ArrayRef) -> Result<()> {
808 self.null_count += array.null_count() as u32;
809 self.nan_count += Self::count_nans(array);
810 self.min.update_batch(std::slice::from_ref(array))?;
811 self.max.update_batch(std::slice::from_ref(array))?;
812 Ok(())
813 }
814
815 fn finish_zone(&mut self, bound: ZoneBound) -> Result<Self::ZoneStatistics> {
816 Ok(ZoneMapStatistics {
817 min: self.min.evaluate()?,
818 max: self.max.evaluate()?,
819 null_count: self.null_count,
820 nan_count: self.nan_count,
821 bound,
822 })
823 }
824
825 fn reset(&mut self) -> Result<()> {
826 self.min = MinAccumulator::try_new(&self.data_type)?;
827 self.max = MaxAccumulator::try_new(&self.data_type)?;
828 self.null_count = 0;
829 self.nan_count = 0;
830 Ok(())
831 }
832}
833
834#[derive(Debug, Default)]
835pub struct ZoneMapIndexPlugin;
836
837impl ZoneMapIndexPlugin {
838 async fn train_zonemap_index(
839 batches_source: SendableRecordBatchStream,
840 index_store: &dyn IndexStore,
841 options: Option<ZoneMapIndexBuilderParams>,
842 ) -> Result<()> {
843 let value_type = batches_source.schema().field(0).data_type().clone();
845
846 let mut builder = ZoneMapIndexBuilder::try_new(options.unwrap_or_default(), value_type)?;
847
848 builder.train(batches_source).await?;
849
850 builder.write_index(index_store).await?;
851 Ok(())
852 }
853}
854
855pub struct ZoneMapIndexTrainingRequest {
856 pub params: ZoneMapIndexBuilderParams,
857 pub criteria: TrainingCriteria,
858}
859
860impl ZoneMapIndexTrainingRequest {
861 pub fn new(params: ZoneMapIndexBuilderParams) -> Self {
862 Self {
863 params,
864 criteria: TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
865 }
866 }
867}
868
869impl TrainingRequest for ZoneMapIndexTrainingRequest {
870 fn as_any(&self) -> &dyn std::any::Any {
871 self
872 }
873 fn criteria(&self) -> &TrainingCriteria {
874 &self.criteria
875 }
876}
877
878#[async_trait]
879impl ScalarIndexPlugin for ZoneMapIndexPlugin {
880 fn name(&self) -> &str {
881 "ZoneMap"
882 }
883
884 fn new_training_request(
885 &self,
886 params: &str,
887 field: &Field,
888 ) -> Result<Box<dyn TrainingRequest>> {
889 if field.data_type().is_nested() {
890 return Err(Error::invalid_input_source(
891 "A zone map index can only be created on a non-nested field.".into(),
892 ));
893 }
894
895 let params = serde_json::from_str::<ZoneMapIndexBuilderParams>(params)?;
896
897 Ok(Box::new(ZoneMapIndexTrainingRequest::new(params)))
898 }
899
900 fn provides_exact_answer(&self) -> bool {
901 false
902 }
903
904 fn version(&self) -> u32 {
905 ZONEMAP_INDEX_VERSION
906 }
907
908 fn new_query_parser(
909 &self,
910 index_name: String,
911 _index_details: &prost_types::Any,
912 ) -> Option<Box<dyn ScalarQueryParser>> {
913 Some(Box::new(SargableQueryParser::new(
914 index_name,
915 self.name().to_string(),
916 true,
917 )))
918 }
919
920 async fn train_index(
921 &self,
922 data: SendableRecordBatchStream,
923 index_store: &dyn IndexStore,
924 request: Box<dyn TrainingRequest>,
925 _fragment_ids: Option<Vec<u32>>,
926 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
927 ) -> Result<CreatedIndex> {
928 let request = (request as Box<dyn std::any::Any>)
929 .downcast::<ZoneMapIndexTrainingRequest>()
930 .map_err(|_| {
931 Error::invalid_input_source(
932 "must provide training request created by new_training_request".into(),
933 )
934 })?;
935 Self::train_zonemap_index(data, index_store, Some(request.params)).await?;
936 Ok(CreatedIndex {
937 index_details: prost_types::Any::from_msg(&pbold::ZoneMapIndexDetails::default())
938 .unwrap(),
939 index_version: ZONEMAP_INDEX_VERSION,
940 files: Some(index_store.list_files_with_sizes().await?),
941 })
942 }
943
944 async fn load_index(
945 &self,
946 index_store: Arc<dyn IndexStore>,
947 _index_details: &prost_types::Any,
948 frag_reuse_index: Option<Arc<FragReuseIndex>>,
949 cache: &LanceCache,
950 ) -> Result<Arc<dyn ScalarIndex>> {
951 Ok(ZoneMapIndex::load(index_store, frag_reuse_index, cache).await? as Arc<dyn ScalarIndex>)
952 }
953}
954
955#[cfg(test)]
956mod tests {
957 use crate::scalar::registry::VALUE_COLUMN_NAME;
958 use crate::scalar::{IndexStore, zonemap::ROWS_PER_ZONE_DEFAULT};
959 use std::sync::Arc;
960
961 use crate::scalar::zoned::ZoneBound;
962 use crate::scalar::zonemap::{ZoneMapIndexPlugin, ZoneMapStatistics};
963 use arrow::datatypes::Float32Type;
964 use arrow_array::{Array, RecordBatch, UInt64Array, record_batch};
965 use arrow_schema::{DataType, Field, Schema};
966 use datafusion::execution::SendableRecordBatchStream;
967 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
968 use datafusion_common::ScalarValue;
969 use futures::{StreamExt, TryStreamExt, stream};
970 use lance_core::utils::mask::NullableRowAddrSet;
971 use lance_core::utils::tempfile::TempObjDir;
972 use lance_core::{
973 ROW_ADDR,
974 cache::{LanceCache, WeakLanceCache},
975 utils::mask::RowAddrTreeMap,
976 };
977 use lance_datafusion::datagen::DatafusionDatagenExt;
978 use lance_datagen::ArrayGeneratorExt;
979 use lance_datagen::{BatchCount, RowCount, array};
980 use lance_io::object_store::ObjectStore;
981
982 use crate::scalar::{
983 SargableQuery, ScalarIndex, SearchResult,
984 lance_format::LanceIndexStore,
985 zonemap::{
986 ZONEMAP_FILENAME, ZONEMAP_SIZE_META_KEY, ZoneMapIndex, ZoneMapIndexBuilderParams,
987 },
988 };
989
990 use crate::Index; use crate::metrics::NoOpMetricsCollector;
993 use roaring::RoaringBitmap; use std::collections::Bound;
995
996 fn add_row_addr(stream: SendableRecordBatchStream) -> SendableRecordBatchStream {
998 let schema = stream.schema();
999 let schema_with_row_addr = Arc::new(Schema::new(vec![
1000 schema.field(0).clone(),
1001 Field::new(ROW_ADDR, DataType::UInt64, false),
1002 ]));
1003 let schema = schema_with_row_addr.clone();
1004 let stream = stream.enumerate().map(move |(frag_id, batch)| {
1005 let batch = batch.unwrap();
1006 let row_addr = Arc::new(UInt64Array::from_iter_values(
1007 (0..batch.num_rows() as u64).map(|off| off + ((frag_id as u64) << 32)),
1008 ));
1009 Ok(RecordBatch::try_new(
1010 schema_with_row_addr.clone(),
1011 vec![batch.column(0).clone(), row_addr],
1012 )?)
1013 });
1014 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
1015 }
1016
1017 #[tokio::test]
1018 async fn test_empty_zonemap_index() {
1019 let tmpdir = TempObjDir::default();
1020 let test_store = Arc::new(LanceIndexStore::new(
1021 Arc::new(ObjectStore::local()),
1022 tmpdir.clone(),
1023 Arc::new(LanceCache::no_cache()),
1024 ));
1025
1026 let data = arrow_array::Int32Array::from(Vec::<i32>::new());
1027 let row_ids = arrow_array::UInt64Array::from(Vec::<u64>::new());
1028 let schema = Arc::new(Schema::new(vec![
1029 Field::new(VALUE_COLUMN_NAME, DataType::Int32, false),
1030 Field::new(ROW_ADDR, DataType::UInt64, false),
1031 ]));
1032 let data =
1033 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
1034
1035 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1036 schema,
1037 stream::once(std::future::ready(Ok(data))),
1038 ));
1039
1040 ZoneMapIndexPlugin::train_zonemap_index(data_stream, test_store.as_ref(), None)
1041 .await
1042 .unwrap();
1043
1044 log::debug!("Successfully wrote the index file");
1045
1046 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1048 .await
1049 .expect("Failed to load ZoneMapIndex");
1050 assert_eq!(index.zones.len(), 0);
1051 assert_eq!(index.data_type, DataType::Int32);
1052 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
1053
1054 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1056 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1057 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1058 }
1059
1060 #[tokio::test]
1061 async fn test_null_zonemap_index() {
1063 let tmpdir = TempObjDir::default();
1064 let test_store = Arc::new(LanceIndexStore::new(
1065 Arc::new(ObjectStore::local()),
1066 tmpdir.clone(),
1067 Arc::new(LanceCache::no_cache()),
1068 ));
1069
1070 let stream = lance_datagen::gen_batch()
1071 .col(
1072 VALUE_COLUMN_NAME,
1073 array::rand::<Float32Type>().with_nulls(&[true, false, false, false, false]),
1074 )
1075 .into_df_stream(RowCount::from(5000), BatchCount::from(10));
1076
1077 let stream = add_row_addr(stream);
1079
1080 ZoneMapIndexPlugin::train_zonemap_index(
1081 stream,
1082 test_store.as_ref(),
1083 Some(ZoneMapIndexBuilderParams::new(5000)),
1084 )
1085 .await
1086 .unwrap();
1087
1088 log::debug!("Successfully wrote the index file");
1089
1090 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1092 .await
1093 .expect("Failed to load ZoneMapIndex");
1094 assert_eq!(index.zones.len(), 10);
1095 for (i, zone) in index.zones.iter().enumerate() {
1096 assert_eq!(zone.null_count, 1000);
1097 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1098 assert_eq!(zone.bound.length, 5000);
1099 assert_eq!(zone.bound.fragment_id, i as u64);
1100 }
1101
1102 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1104 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1105
1106 let mut expected = RowAddrTreeMap::new();
1108 for fragment_id in 0..10 {
1109 let start = (fragment_id as u64) << 32;
1110 let end = start + 5000;
1111 expected.insert_range(start..end);
1112 }
1113 assert_eq!(result, SearchResult::at_most(expected));
1114
1115 let new_data =
1117 arrow_array::Float32Array::from_iter_values((0..5000).map(|i| i as f32 / 1000.0));
1118 let new_row_addr =
1120 UInt64Array::from_iter_values((0..5000).map(|i| (10u64 << 32) | (i as u64)));
1121 let new_schema = Arc::new(Schema::new(vec![
1122 Field::new(VALUE_COLUMN_NAME, DataType::Float32, false), Field::new(ROW_ADDR, DataType::UInt64, false), ]));
1125 let new_data_batch = RecordBatch::try_new(
1126 new_schema.clone(),
1127 vec![Arc::new(new_data), Arc::new(new_row_addr)],
1128 )
1129 .unwrap();
1130 let new_data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1131 new_schema,
1132 stream::once(std::future::ready(Ok(new_data_batch))),
1133 ));
1134
1135 index
1138 .update(new_data_stream, test_store.as_ref(), None)
1139 .await
1140 .unwrap();
1141
1142 let updated_index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1144 .await
1145 .expect("Failed to load updated ZoneMapIndex");
1146
1147 assert_eq!(updated_index.zones.len(), 11);
1149
1150 let new_zone = &updated_index.zones[10]; assert_eq!(new_zone.bound.fragment_id, 10u64); assert_eq!(new_zone.bound.length, 5000);
1154 assert_eq!(new_zone.null_count, 0); assert_eq!(new_zone.nan_count, 0); let query = SargableQuery::Equals(ScalarValue::Float32(None));
1159 let result = updated_index
1160 .search(&query, &NoOpMetricsCollector)
1161 .await
1162 .unwrap();
1163
1164 let mut expected = RowAddrTreeMap::new();
1166 for fragment_id in 0..10 {
1167 let start = (fragment_id as u64) << 32;
1168 let end = start + 5000;
1169 expected.insert_range(start..end);
1170 }
1171 assert_eq!(result, SearchResult::at_most(expected));
1172
1173 let query = SargableQuery::Equals(ScalarValue::Float32(Some(2.5))); let result = updated_index
1176 .search(&query, &NoOpMetricsCollector)
1177 .await
1178 .unwrap();
1179
1180 let mut expected = RowAddrTreeMap::new();
1182 let start = 10u64 << 32;
1183 let end = start + 5000;
1184 expected.insert_range(start..end);
1185 assert_eq!(result, SearchResult::at_most(expected));
1186 }
1187
1188 #[tokio::test]
1189 async fn test_zonemap_null_handling_in_queries() {
1190 let tmpdir = TempObjDir::default();
1192 let store = Arc::new(LanceIndexStore::new(
1193 Arc::new(ObjectStore::local()),
1194 tmpdir.clone(),
1195 Arc::new(LanceCache::no_cache()),
1196 ));
1197
1198 let batch = record_batch!(
1200 (VALUE_COLUMN_NAME, Int64, [Some(0), Some(5), None]),
1201 (ROW_ADDR, UInt64, [0, 1, 2])
1202 )
1203 .unwrap();
1204 let schema = batch.schema();
1205 let stream = stream::once(async move { Ok(batch) });
1206 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
1207
1208 ZoneMapIndexPlugin::train_zonemap_index(stream, store.as_ref(), None)
1210 .await
1211 .unwrap();
1212
1213 let cache = LanceCache::with_capacity(1024 * 1024);
1214 let index = ZoneMapIndex::load(store.clone(), None, &cache)
1215 .await
1216 .unwrap();
1217
1218 let query = SargableQuery::Equals(ScalarValue::Int64(Some(5)));
1221 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1222
1223 match result {
1224 SearchResult::AtMost(row_ids) => {
1225 let all_rows: Vec<u64> = row_ids
1228 .true_rows()
1229 .row_addrs()
1230 .unwrap()
1231 .map(u64::from)
1232 .collect();
1233 assert_eq!(
1234 all_rows,
1235 vec![0, 1, 2],
1236 "Should return all rows (including nulls) since ZoneMap is inexact"
1237 );
1238
1239 }
1242 _ => panic!("Expected AtMost search result from zonemap"),
1243 }
1244
1245 let query = SargableQuery::Range(
1247 std::ops::Bound::Included(ScalarValue::Int64(Some(0))),
1248 std::ops::Bound::Included(ScalarValue::Int64(Some(3))),
1249 );
1250 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1251
1252 match result {
1253 SearchResult::AtMost(row_ids) => {
1254 let all_rows: Vec<u64> = row_ids
1256 .true_rows()
1257 .row_addrs()
1258 .unwrap()
1259 .map(u64::from)
1260 .collect();
1261 assert_eq!(
1262 all_rows,
1263 vec![0, 1, 2],
1264 "Should return all rows in zone as possible matches"
1265 );
1266 }
1267 _ => panic!("Expected AtMost search result from zonemap"),
1268 }
1269 }
1270
1271 #[tokio::test]
1272 async fn test_nan_zonemap_index() {
1273 let tmpdir = TempObjDir::default();
1274 let test_store = Arc::new(LanceIndexStore::new(
1275 Arc::new(ObjectStore::local()),
1276 tmpdir.clone(),
1277 Arc::new(LanceCache::no_cache()),
1278 ));
1279
1280 let mut values = Vec::new();
1283 for i in 0..500 {
1284 if i % 5 == 2 {
1285 values.push(f32::NAN);
1286 } else {
1287 values.push(i as f32);
1289 }
1290 }
1291
1292 let float_data = arrow_array::Float32Array::from(values);
1293 let row_ids = UInt64Array::from_iter_values((0..float_data.len()).map(|i| i as u64));
1294 let schema = Arc::new(Schema::new(vec![
1295 Field::new(VALUE_COLUMN_NAME, DataType::Float32, true),
1296 Field::new(ROW_ADDR, DataType::UInt64, false),
1297 ]));
1298 let data = RecordBatch::try_new(
1299 schema.clone(),
1300 vec![Arc::new(float_data.clone()), Arc::new(row_ids)],
1301 )
1302 .unwrap();
1303 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1304 schema,
1305 stream::once(std::future::ready(Ok(data))),
1306 ));
1307
1308 ZoneMapIndexPlugin::train_zonemap_index(
1309 data_stream,
1310 test_store.as_ref(),
1311 Some(ZoneMapIndexBuilderParams::new(100)),
1312 )
1313 .await
1314 .unwrap();
1315
1316 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1318 .await
1319 .expect("Failed to load ZoneMapIndex");
1320
1321 assert_eq!(index.zones.len(), 5);
1323
1324 for (i, zone) in index.zones.iter().enumerate() {
1328 assert_eq!(zone.nan_count, 20, "Zone {} should have 20 NaN values", i);
1329 assert_eq!(
1330 zone.bound.length, 100,
1331 "Zone {} should have zone_length 100",
1332 i
1333 );
1334 assert_eq!(
1335 zone.bound.fragment_id, 0u64,
1336 "Zone {} should have fragment_id 0",
1337 i
1338 );
1339 }
1340
1341 let query = SargableQuery::Equals(ScalarValue::Float32(Some(f32::NAN)));
1343 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1344
1345 let mut expected = RowAddrTreeMap::new();
1347 expected.insert_range(0..500); assert_eq!(result, SearchResult::at_most(expected));
1349
1350 let query = SargableQuery::Equals(ScalarValue::Float32(Some(5.0)));
1352 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1353
1354 let mut expected = RowAddrTreeMap::new();
1356 expected.insert_range(0..100);
1357 assert_eq!(result, SearchResult::at_most(expected));
1358
1359 let query = SargableQuery::Equals(ScalarValue::Float32(Some(1000.0)));
1361 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1362
1363 let mut expected = RowAddrTreeMap::new();
1366 expected.insert_range(0..500);
1367 assert_eq!(result, SearchResult::at_most(expected));
1368
1369 let query = SargableQuery::Range(
1371 Bound::Included(ScalarValue::Float32(Some(0.0))),
1372 Bound::Included(ScalarValue::Float32(Some(250.0))),
1373 );
1374 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1375
1376 let mut expected = RowAddrTreeMap::new();
1378 expected.insert_range(0..300);
1379 assert_eq!(result, SearchResult::at_most(expected));
1380
1381 let query = SargableQuery::IsIn(vec![
1383 ScalarValue::Float32(Some(f32::NAN)),
1384 ScalarValue::Float32(Some(5.0)),
1385 ScalarValue::Float32(Some(150.0)), ]);
1387 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1388
1389 let mut expected = RowAddrTreeMap::new();
1391 expected.insert_range(0..500);
1392 assert_eq!(result, SearchResult::at_most(expected));
1393
1394 let query = SargableQuery::Range(
1396 Bound::Included(ScalarValue::Float32(Some(1000.0))),
1397 Bound::Included(ScalarValue::Float32(Some(2000.0))),
1398 );
1399 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1400
1401 let mut expected = RowAddrTreeMap::new();
1404 expected.insert_range(0..500);
1405 assert_eq!(result, SearchResult::at_most(expected));
1406
1407 let query = SargableQuery::IsNull();
1409 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1410 assert_eq!(result, SearchResult::AtMost(NullableRowAddrSet::empty()));
1411
1412 let query = SargableQuery::Range(
1415 Bound::Included(ScalarValue::Float32(Some(f32::NAN))),
1416 Bound::Unbounded,
1417 );
1418 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1419 let mut expected = RowAddrTreeMap::new();
1421 expected.insert_range(0..500);
1422 assert_eq!(result, SearchResult::at_most(expected));
1423
1424 let query = SargableQuery::Range(
1426 Bound::Unbounded,
1427 Bound::Included(ScalarValue::Float32(Some(f32::NAN))),
1428 );
1429 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1430 let mut expected = RowAddrTreeMap::new();
1432 expected.insert_range(0..500);
1433 assert_eq!(result, SearchResult::at_most(expected));
1434
1435 let query = SargableQuery::Range(
1437 Bound::Unbounded,
1438 Bound::Excluded(ScalarValue::Float32(Some(f32::NAN))),
1439 );
1440 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1441 let mut expected = RowAddrTreeMap::new();
1443 expected.insert_range(0..500);
1444 assert_eq!(result, SearchResult::at_most(expected));
1445
1446 let query = SargableQuery::Range(
1448 Bound::Excluded(ScalarValue::Float32(Some(f32::NAN))),
1449 Bound::Unbounded,
1450 );
1451 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1452 assert_eq!(result, SearchResult::AtMost(NullableRowAddrSet::empty()));
1454
1455 let query = SargableQuery::IsIn(vec![
1457 ScalarValue::Float16(Some(half::f16::NAN)),
1458 ScalarValue::Float32(Some(f32::NAN)),
1459 ScalarValue::Float64(Some(f64::NAN)),
1460 ScalarValue::Float32(Some(5.0)),
1461 ]);
1462 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1463 let mut expected = RowAddrTreeMap::new();
1465 expected.insert_range(0..500);
1466 assert_eq!(result, SearchResult::at_most(expected));
1467 }
1468
1469 #[tokio::test]
1470 async fn test_basic_zonemap_index() {
1472 let tmpdir = TempObjDir::default();
1473 let test_store = Arc::new(LanceIndexStore::new(
1474 Arc::new(ObjectStore::local()),
1475 tmpdir.clone(),
1476 Arc::new(LanceCache::no_cache()),
1477 ));
1478
1479 let data = arrow_array::Int32Array::from_iter_values(0..=100);
1480 let row_ids = UInt64Array::from_iter_values((0..data.len()).map(|i| i as u64));
1481 let schema = Arc::new(Schema::new(vec![
1482 Field::new(VALUE_COLUMN_NAME, DataType::Int32, false),
1483 Field::new(ROW_ADDR, DataType::UInt64, false),
1484 ]));
1485 let data =
1486 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
1487 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1488 schema,
1489 stream::once(std::future::ready(Ok(data))),
1490 ));
1491
1492 ZoneMapIndexPlugin::train_zonemap_index(
1493 data_stream,
1494 test_store.as_ref(),
1495 Some(ZoneMapIndexBuilderParams::new(100)),
1496 )
1497 .await
1498 .unwrap();
1499
1500 log::debug!("Successfully wrote the index file");
1501
1502 let index_file = test_store.open_index_file(ZONEMAP_FILENAME).await.unwrap();
1504 let metadata = index_file.schema().metadata.clone();
1506 let record_batch = index_file
1507 .read_record_batch(0, index_file.num_rows() as u64)
1508 .await
1509 .unwrap();
1510 assert_eq!(record_batch.num_rows(), 2);
1511 assert_eq!(
1512 record_batch
1513 .column(0)
1514 .as_any()
1515 .downcast_ref::<arrow_array::Int32Array>()
1516 .unwrap()
1517 .values(),
1518 &[0, 100]
1519 );
1520 assert_eq!(
1521 record_batch
1522 .column(1)
1523 .as_any()
1524 .downcast_ref::<arrow_array::Int32Array>()
1525 .unwrap()
1526 .values(),
1527 &[99, 100]
1528 );
1529 assert_eq!(
1530 record_batch
1531 .column(2)
1532 .as_any()
1533 .downcast_ref::<arrow_array::UInt32Array>()
1534 .unwrap()
1535 .values(),
1536 &[0, 0]
1537 );
1538 assert_eq!(metadata.get(ZONEMAP_SIZE_META_KEY).unwrap(), "100");
1539
1540 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1542 .await
1543 .expect("Failed to load ZoneMapIndex");
1544 assert_eq!(index.zones.len(), 2);
1545 assert_eq!(
1546 index.zones,
1547 vec![
1548 ZoneMapStatistics {
1549 min: ScalarValue::Int32(Some(0)),
1550 max: ScalarValue::Int32(Some(99)),
1551 null_count: 0,
1552 nan_count: 0,
1553 bound: ZoneBound {
1554 fragment_id: 0,
1555 start: 0,
1556 length: 100,
1557 },
1558 },
1559 ZoneMapStatistics {
1560 min: ScalarValue::Int32(Some(100)),
1561 max: ScalarValue::Int32(Some(100)),
1562 null_count: 0,
1563 nan_count: 0,
1564 bound: ZoneBound {
1565 fragment_id: 0,
1566 start: 100,
1567 length: 1,
1568 },
1569 }
1570 ]
1571 );
1572 for (i, zone) in index.zones.iter().enumerate() {
1574 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1575 }
1576
1577 assert_eq!(index.data_type, DataType::Int32);
1578 assert_eq!(index.rows_per_zone, 100);
1579 assert_eq!(
1580 index.calculate_included_frags().await.unwrap(),
1581 RoaringBitmap::from_iter(0..1)
1582 );
1583
1584 let query = SargableQuery::Range(
1588 Bound::Excluded(ScalarValue::Int32(Some(50))),
1589 Bound::Unbounded,
1590 );
1591 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1592 assert_eq!(result, SearchResult::at_most(0..=100));
1593
1594 let query = SargableQuery::Range(
1596 Bound::Included(ScalarValue::Int32(Some(0))),
1597 Bound::Included(ScalarValue::Int32(Some(50))),
1598 );
1599 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1600 assert_eq!(result, SearchResult::at_most(0..=99));
1601
1602 let query = SargableQuery::Range(
1604 Bound::Included(ScalarValue::Int32(Some(101))),
1605 Bound::Included(ScalarValue::Int32(Some(200))),
1606 );
1607 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1608 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1610
1611 let query = SargableQuery::Range(
1613 Bound::Included(ScalarValue::Int32(Some(100))),
1614 Bound::Included(ScalarValue::Int32(Some(100))),
1615 );
1616 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1617 assert_eq!(result, SearchResult::at_most(100..=100));
1618
1619 let query = SargableQuery::Equals(ScalarValue::Int32(Some(0)));
1621 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1622 assert_eq!(result, SearchResult::at_most(0..=99));
1623
1624 let query = SargableQuery::Equals(ScalarValue::Int32(Some(100)));
1626 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1627 assert_eq!(result, SearchResult::at_most(100..=100));
1628
1629 let query = SargableQuery::Equals(ScalarValue::Int32(Some(101)));
1631 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1632 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1633
1634 let query = SargableQuery::IsNull();
1636 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1637 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1638 let query = SargableQuery::IsIn(vec![
1640 ScalarValue::Int32(Some(0)),
1641 ScalarValue::Int32(Some(100)),
1642 ScalarValue::Int32(Some(101)),
1643 ScalarValue::Int32(Some(50)),
1644 ]);
1645 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1646 assert_eq!(result, SearchResult::at_most(0..=100));
1648
1649 let query = SargableQuery::IsIn(vec![
1651 ScalarValue::Int32(Some(101)),
1652 ScalarValue::Int32(Some(102)),
1653 ]);
1654 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1655 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1656
1657 let query = SargableQuery::IsIn(vec![ScalarValue::Int32(None)]);
1659 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1660 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1661
1662 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1664 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1665 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1666 }
1667
1668 #[tokio::test]
1669 async fn test_complex_zonemap_index() {
1671 let tmpdir = TempObjDir::default();
1672 let test_store = Arc::new(LanceIndexStore::new(
1673 Arc::new(ObjectStore::local()),
1674 tmpdir.clone(),
1675 Arc::new(LanceCache::no_cache()),
1676 ));
1677
1678 let data =
1680 arrow_array::Int64Array::from_iter_values(0..(ROWS_PER_ZONE_DEFAULT * 2 + 42) as i64);
1681 let row_ids = UInt64Array::from_iter_values((0..data.len()).map(|i| i as u64));
1682 let schema = Arc::new(Schema::new(vec![
1683 Field::new(VALUE_COLUMN_NAME, DataType::Int64, false),
1684 Field::new(ROW_ADDR, DataType::UInt64, false),
1685 ]));
1686 let data =
1687 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
1688 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1689 schema,
1690 stream::once(std::future::ready(Ok(data))),
1691 ));
1692
1693 ZoneMapIndexPlugin::train_zonemap_index(
1694 data_stream,
1695 test_store.as_ref(),
1696 Some(ZoneMapIndexBuilderParams::default()),
1697 )
1698 .await
1699 .unwrap();
1700
1701 log::debug!("Successfully wrote the index file");
1702
1703 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1705 .await
1706 .expect("Failed to load ZoneMapIndex");
1707 assert_eq!(index.zones.len(), 3);
1708 assert_eq!(
1709 index.zones,
1710 vec![
1711 ZoneMapStatistics {
1712 min: ScalarValue::Int64(Some(0)),
1713 max: ScalarValue::Int64(Some(8191)),
1714 null_count: 0,
1715 nan_count: 0,
1716 bound: ZoneBound {
1717 fragment_id: 0,
1718 start: 0,
1719 length: 8192,
1720 },
1721 },
1722 ZoneMapStatistics {
1723 min: ScalarValue::Int64(Some(8192)),
1724 max: ScalarValue::Int64(Some(16383)),
1725 null_count: 0,
1726 nan_count: 0,
1727 bound: ZoneBound {
1728 fragment_id: 0,
1729 start: 8192,
1730 length: 8192,
1731 },
1732 },
1733 ZoneMapStatistics {
1734 min: ScalarValue::Int64(Some(16384)),
1735 max: ScalarValue::Int64(Some(16425)),
1736 null_count: 0,
1737 nan_count: 0,
1738 bound: ZoneBound {
1739 fragment_id: 0,
1740 start: 16384,
1741 length: 42,
1742 },
1743 }
1744 ]
1745 );
1746 for (i, zone) in index.zones.iter().enumerate() {
1748 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1749 }
1750
1751 assert_eq!(index.data_type, DataType::Int64);
1752 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
1753 assert_eq!(
1754 index.calculate_included_frags().await.unwrap(),
1755 RoaringBitmap::from_iter(0..1)
1756 );
1757
1758 let query = SargableQuery::Equals(ScalarValue::Int64(Some(1000)));
1763 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1764 let mut expected = RowAddrTreeMap::new();
1766 expected.insert_range(0..=8191);
1767 assert_eq!(result, SearchResult::at_most(expected));
1768
1769 let query = SargableQuery::Equals(ScalarValue::Int64(Some(9000)));
1771 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1772 let mut expected = RowAddrTreeMap::new();
1774 expected.insert_range(8192..=16383);
1775 assert_eq!(result, SearchResult::at_most(expected));
1776
1777 let query = SargableQuery::Equals(ScalarValue::Int64(Some(20000)));
1779 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1780 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1781
1782 let query = SargableQuery::Range(
1784 Bound::Included(ScalarValue::Int64(Some(9000))),
1785 Bound::Included(ScalarValue::Int64(Some(16400))),
1786 );
1787 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1788 let mut expected = RowAddrTreeMap::new();
1790 expected.insert_range(8192..=16425);
1791 assert_eq!(result, SearchResult::at_most(expected));
1792 }
1793
1794 #[tokio::test]
1795 async fn test_multiple_fragments_zonemap() {
1797 let tmpdir = TempObjDir::default();
1798 let test_store = Arc::new(LanceIndexStore::new(
1799 Arc::new(ObjectStore::local()),
1800 tmpdir.clone(),
1801 Arc::new(LanceCache::no_cache()),
1802 ));
1803
1804 let schema = Arc::new(Schema::new(vec![
1805 Field::new(VALUE_COLUMN_NAME, DataType::Int64, false),
1806 Field::new(ROW_ADDR, DataType::UInt64, false),
1807 ]));
1808
1809 let fragment0_data =
1812 arrow_array::Int64Array::from_iter_values(0..ROWS_PER_ZONE_DEFAULT as i64);
1813 let fragment0_row_ids = UInt64Array::from_iter_values(0..ROWS_PER_ZONE_DEFAULT);
1814 let fragment0_batch = RecordBatch::try_new(
1815 schema.clone(),
1816 vec![Arc::new(fragment0_data), Arc::new(fragment0_row_ids)],
1817 )
1818 .unwrap();
1819
1820 let fragment1_data = arrow_array::Int64Array::from_iter_values(
1822 (ROWS_PER_ZONE_DEFAULT as i64)..((ROWS_PER_ZONE_DEFAULT * 2) as i64),
1823 );
1824 let fragment1_row_ids =
1825 UInt64Array::from_iter_values((0..ROWS_PER_ZONE_DEFAULT).map(|i| i + (1 << 32)));
1826 let fragment1_batch = RecordBatch::try_new(
1827 schema.clone(),
1828 vec![Arc::new(fragment1_data), Arc::new(fragment1_row_ids)],
1829 )
1830 .unwrap();
1831
1832 let fragment2_data = arrow_array::Int64Array::from_iter_values(
1834 ((ROWS_PER_ZONE_DEFAULT * 2) as i64)..((ROWS_PER_ZONE_DEFAULT * 2 + 42) as i64),
1835 );
1836 let fragment2_row_ids =
1837 UInt64Array::from_iter_values((0..42).map(|i| (i as u64) + (2 << 32)));
1838 let fragment2_batch = RecordBatch::try_new(
1839 schema.clone(),
1840 vec![Arc::new(fragment2_data), Arc::new(fragment2_row_ids)],
1841 )
1842 .unwrap();
1843
1844 {
1846 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1848 schema.clone(),
1849 stream::iter(vec![
1850 Ok(fragment0_batch.clone()),
1851 Ok(fragment1_batch.clone()),
1852 Ok(fragment2_batch.clone()),
1853 ]),
1854 ));
1855 ZoneMapIndexPlugin::train_zonemap_index(
1856 data_stream,
1857 test_store.as_ref(),
1858 Some(ZoneMapIndexBuilderParams::new(5000)),
1859 )
1860 .await
1861 .unwrap();
1862
1863 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1865 .await
1866 .expect("Failed to load ZoneMapIndex");
1867 assert_eq!(index.zones.len(), 5);
1868 assert_eq!(
1869 index.zones,
1870 vec![
1871 ZoneMapStatistics {
1872 min: ScalarValue::Int64(Some(0)),
1873 max: ScalarValue::Int64(Some(4999)),
1874 null_count: 0,
1875 nan_count: 0,
1876 bound: ZoneBound {
1877 fragment_id: 0,
1878 start: 0,
1879 length: 5000,
1880 },
1881 },
1882 ZoneMapStatistics {
1883 min: ScalarValue::Int64(Some(5000)),
1884 max: ScalarValue::Int64(Some(8191)),
1885 null_count: 0,
1886 nan_count: 0,
1887 bound: ZoneBound {
1888 fragment_id: 0,
1889 start: 5000,
1890 length: 3192,
1891 },
1892 },
1893 ZoneMapStatistics {
1894 min: ScalarValue::Int64(Some(8192)),
1895 max: ScalarValue::Int64(Some(13191)),
1896 null_count: 0,
1897 nan_count: 0,
1898 bound: ZoneBound {
1899 fragment_id: 1,
1900 start: 0,
1901 length: 5000,
1902 },
1903 },
1904 ZoneMapStatistics {
1905 min: ScalarValue::Int64(Some(13192)),
1906 max: ScalarValue::Int64(Some(16383)),
1907 null_count: 0,
1908 nan_count: 0,
1909 bound: ZoneBound {
1910 fragment_id: 1,
1911 start: 5000,
1912 length: 3192,
1913 },
1914 },
1915 ZoneMapStatistics {
1916 min: ScalarValue::Int64(Some(16384)),
1917 max: ScalarValue::Int64(Some(16425)),
1918 null_count: 0,
1919 nan_count: 0,
1920 bound: ZoneBound {
1921 fragment_id: 2,
1922 start: 0,
1923 length: 42,
1924 },
1925 }
1926 ]
1927 );
1928 for (i, zone) in index.zones.iter().enumerate() {
1930 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1931 }
1932
1933 assert_eq!(index.data_type, DataType::Int64);
1934 assert_eq!(index.rows_per_zone, 5000);
1935 assert_eq!(
1936 index.calculate_included_frags().await.unwrap(),
1937 RoaringBitmap::from_iter(0..3)
1938 );
1939
1940 let verify_data_stream: SendableRecordBatchStream =
1942 Box::pin(RecordBatchStreamAdapter::new(
1943 schema.clone(),
1944 stream::iter(vec![
1945 Ok(fragment0_batch.clone()),
1946 Ok(fragment1_batch.clone()),
1947 Ok(fragment2_batch.clone()),
1948 ]),
1949 ));
1950 let batches: Vec<RecordBatch> = verify_data_stream.try_collect().await.unwrap();
1951
1952 assert_eq!(batches.len(), 3);
1953
1954 let fragment0_rowaddr_col = batches[0].column_by_name(ROW_ADDR).unwrap();
1956 let fragment0_rowaddrs = fragment0_rowaddr_col
1957 .as_any()
1958 .downcast_ref::<UInt64Array>()
1959 .unwrap();
1960 assert_eq!(
1961 fragment0_rowaddrs.values().len(),
1962 ROWS_PER_ZONE_DEFAULT as usize
1963 );
1964 assert_eq!(fragment0_rowaddrs.values()[0], 0);
1965 assert_eq!(
1966 fragment0_rowaddrs.values()[fragment0_rowaddrs.values().len() - 1],
1967 8191
1968 );
1969
1970 let fragment1_rowaddr_col = batches[1].column_by_name(ROW_ADDR).unwrap();
1972 let fragment1_rowaddrs = fragment1_rowaddr_col
1973 .as_any()
1974 .downcast_ref::<UInt64Array>()
1975 .unwrap();
1976 assert_eq!(
1977 fragment1_rowaddrs.values().len(),
1978 ROWS_PER_ZONE_DEFAULT as usize
1979 );
1980 assert_eq!(fragment1_rowaddrs.values()[0], 1u64 << 32); assert_eq!(
1982 fragment1_rowaddrs.values()[fragment1_rowaddrs.values().len() - 1],
1983 8191 | (1u64 << 32)
1984 );
1985
1986 let fragment2_rowaddr_col = batches[2].column_by_name(ROW_ADDR).unwrap();
1988 let fragment2_rowaddrs = fragment2_rowaddr_col
1989 .as_any()
1990 .downcast_ref::<UInt64Array>()
1991 .unwrap();
1992 assert_eq!(fragment2_rowaddrs.values().len(), 42);
1993 assert_eq!(fragment2_rowaddrs.values()[0], 2u64 << 32); assert_eq!(
1995 fragment2_rowaddrs.values()[fragment2_rowaddrs.values().len() - 1],
1996 (2u64 << 32) | 41
1997 );
1998
1999 let query = SargableQuery::Range(
2003 Bound::Included(ScalarValue::Int64(Some(5000))),
2004 Bound::Included(ScalarValue::Int64(Some(12000))),
2005 );
2006 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2007 let mut expected = RowAddrTreeMap::new();
2009 expected.insert_range(5000..8192);
2011 expected.insert_range((1u64 << 32)..((1u64 << 32) + 5000));
2013 assert_eq!(result, SearchResult::at_most(expected));
2014
2015 let query = SargableQuery::Equals(ScalarValue::Int64(Some(8192)));
2017 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2018 let mut expected = RowAddrTreeMap::new();
2020 expected.insert_range((1u64 << 32)..((1u64 << 32) + 5000));
2021 assert_eq!(result, SearchResult::at_most(expected));
2022
2023 let query = SargableQuery::Equals(ScalarValue::Int64(Some(16385)));
2025 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2026 let mut expected = RowAddrTreeMap::new();
2028 expected.insert_range(2u64 << 32..((2u64 << 32) + 42));
2029 assert_eq!(result, SearchResult::at_most(expected));
2030
2031 let query = SargableQuery::Equals(ScalarValue::Int64(Some(99999)));
2033 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2034 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
2035
2036 let query = SargableQuery::IsIn(vec![ScalarValue::Int64(Some(16385))]);
2038 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2039 let mut expected = RowAddrTreeMap::new();
2040 expected.insert_range(2u64 << 32..((2u64 << 32) + 42));
2041 assert_eq!(result, SearchResult::at_most(expected));
2042
2043 let query = SargableQuery::Equals(ScalarValue::Int64(None));
2045 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2046 let mut expected = RowAddrTreeMap::new();
2047 expected.insert_range(0..=16425);
2048 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
2050 }
2051
2052 {
2054 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2056 schema.clone(),
2057 stream::iter(vec![
2058 Ok(fragment0_batch.clone()),
2059 Ok(fragment1_batch.clone()),
2060 Ok(fragment2_batch.clone()),
2061 ]),
2062 ));
2063 ZoneMapIndexPlugin::train_zonemap_index(
2064 data_stream,
2065 test_store.as_ref(),
2066 Some(ZoneMapIndexBuilderParams::default()),
2067 )
2068 .await
2069 .unwrap();
2070
2071 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2073 .await
2074 .expect("Failed to load ZoneMapIndex");
2075 assert_eq!(index.zones.len(), 3);
2076 assert_eq!(
2077 index.zones,
2078 vec![
2079 ZoneMapStatistics {
2080 min: ScalarValue::Int64(Some(0)),
2081 max: ScalarValue::Int64(Some(8191)),
2082 null_count: 0,
2083 nan_count: 0,
2084 bound: ZoneBound {
2085 fragment_id: 0,
2086 start: 0,
2087 length: 8192,
2088 },
2089 },
2090 ZoneMapStatistics {
2091 min: ScalarValue::Int64(Some(8192)),
2092 max: ScalarValue::Int64(Some(16383)),
2093 null_count: 0,
2094 nan_count: 0,
2095 bound: ZoneBound {
2096 fragment_id: 1,
2097 start: 0,
2098 length: 8192,
2099 },
2100 },
2101 ZoneMapStatistics {
2102 min: ScalarValue::Int64(Some(16384)),
2103 max: ScalarValue::Int64(Some(16425)),
2104 null_count: 0,
2105 nan_count: 0,
2106 bound: ZoneBound {
2107 fragment_id: 2,
2108 start: 0,
2109 length: 42,
2110 },
2111 }
2112 ]
2113 );
2114 for (i, zone) in index.zones.iter().enumerate() {
2116 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
2117 }
2118
2119 assert_eq!(index.data_type, DataType::Int64);
2120 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
2121 assert_eq!(
2122 index.calculate_included_frags().await.unwrap(),
2123 RoaringBitmap::from_iter(0..3)
2124 );
2125 }
2126
2127 {
2129 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2131 schema.clone(),
2132 stream::iter(vec![
2133 Ok(fragment0_batch.clone()),
2134 Ok(fragment1_batch.clone()),
2135 Ok(fragment2_batch.clone()),
2136 ]),
2137 ));
2138 ZoneMapIndexPlugin::train_zonemap_index(
2139 data_stream,
2140 test_store.as_ref(),
2141 Some(ZoneMapIndexBuilderParams::new(ROWS_PER_ZONE_DEFAULT * 3)),
2142 )
2143 .await
2144 .unwrap();
2145
2146 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2148 .await
2149 .expect("Failed to load ZoneMapIndex");
2150 assert_eq!(index.zones.len(), 3);
2151 assert_eq!(
2152 index.zones,
2153 vec![
2154 ZoneMapStatistics {
2155 min: ScalarValue::Int64(Some(0)),
2156 max: ScalarValue::Int64(Some(8191)),
2157 null_count: 0,
2158 nan_count: 0,
2159 bound: ZoneBound {
2160 fragment_id: 0,
2161 start: 0,
2162 length: 8192,
2163 },
2164 },
2165 ZoneMapStatistics {
2166 min: ScalarValue::Int64(Some(8192)),
2167 max: ScalarValue::Int64(Some(16383)),
2168 null_count: 0,
2169 nan_count: 0,
2170 bound: ZoneBound {
2171 fragment_id: 1,
2172 start: 0,
2173 length: 8192,
2174 },
2175 },
2176 ZoneMapStatistics {
2177 min: ScalarValue::Int64(Some(16384)),
2178 max: ScalarValue::Int64(Some(16425)),
2179 null_count: 0,
2180 nan_count: 0,
2181 bound: ZoneBound {
2182 fragment_id: 2,
2183 start: 0,
2184 length: 42,
2185 },
2186 }
2187 ]
2188 );
2189 for (i, zone) in index.zones.iter().enumerate() {
2191 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
2192 }
2193
2194 assert_eq!(index.data_type, DataType::Int64);
2195 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT * 3);
2196 }
2197 }
2198
2199 #[tokio::test]
2200 async fn test_fragment_id_assignment() {
2201 let schema = Arc::new(Schema::new(vec![Field::new(
2203 VALUE_COLUMN_NAME,
2204 DataType::Int32,
2205 false,
2206 )]));
2207
2208 let fragment0_data = arrow_array::Int32Array::from_iter_values(0..5);
2210 let fragment0_batch =
2211 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment0_data)]).unwrap();
2212
2213 let fragment1_data = arrow_array::Int32Array::from_iter_values(5..10);
2214 let fragment1_batch =
2215 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment1_data)]).unwrap();
2216
2217 let aligned_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2218 schema,
2219 stream::iter(vec![Ok(fragment0_batch), Ok(fragment1_batch)]),
2220 ));
2221
2222 let aligned_stream = add_row_addr(aligned_stream);
2223
2224 let batches: Vec<RecordBatch> = aligned_stream.try_collect().await.unwrap();
2225
2226 assert_eq!(batches.len(), 2);
2227
2228 let fragment0_rowaddr_col = batches[0].column_by_name(ROW_ADDR).unwrap();
2230 let fragment0_rowaddrs = fragment0_rowaddr_col
2231 .as_any()
2232 .downcast_ref::<UInt64Array>()
2233 .unwrap();
2234
2235 assert_eq!(fragment0_rowaddrs.values(), &[0, 1, 2, 3, 4]);
2237
2238 let fragment1_rowaddr_col = batches[1].column_by_name(ROW_ADDR).unwrap();
2240 let fragment1_rowaddrs = fragment1_rowaddr_col
2241 .as_any()
2242 .downcast_ref::<UInt64Array>()
2243 .unwrap();
2244
2245 assert_eq!(
2248 fragment1_rowaddrs.values(),
2249 &[4294967296, 4294967297, 4294967298, 4294967299, 4294967300]
2250 );
2251 }
2252
2253 #[tokio::test]
2254 async fn test_like_prefix_query() {
2255 let tmpdir = TempObjDir::default();
2256 let test_store = Arc::new(LanceIndexStore::new(
2257 Arc::new(ObjectStore::local()),
2258 tmpdir.clone(),
2259 Arc::new(LanceCache::no_cache()),
2260 ));
2261
2262 let zones = vec![
2271 ZoneMapStatistics {
2272 min: ScalarValue::Utf8(Some("aaa".to_string())),
2273 max: ScalarValue::Utf8(Some("azz".to_string())),
2274 null_count: 0,
2275 nan_count: 0,
2276 bound: ZoneBound {
2277 fragment_id: 0,
2278 start: 0,
2279 length: 100,
2280 },
2281 },
2282 ZoneMapStatistics {
2283 min: ScalarValue::Utf8(Some("bar".to_string())),
2284 max: ScalarValue::Utf8(Some("baz".to_string())),
2285 null_count: 0,
2286 nan_count: 0,
2287 bound: ZoneBound {
2288 fragment_id: 1,
2289 start: 0,
2290 length: 100,
2291 },
2292 },
2293 ZoneMapStatistics {
2294 min: ScalarValue::Utf8(Some("fa".to_string())),
2295 max: ScalarValue::Utf8(Some("foz".to_string())),
2296 null_count: 0,
2297 nan_count: 0,
2298 bound: ZoneBound {
2299 fragment_id: 2,
2300 start: 0,
2301 length: 100,
2302 },
2303 },
2304 ZoneMapStatistics {
2305 min: ScalarValue::Utf8(Some("fop".to_string())),
2306 max: ScalarValue::Utf8(Some("fzz".to_string())),
2307 null_count: 0,
2308 nan_count: 0,
2309 bound: ZoneBound {
2310 fragment_id: 3,
2311 start: 0,
2312 length: 100,
2313 },
2314 },
2315 ZoneMapStatistics {
2316 min: ScalarValue::Utf8(Some("foo".to_string())),
2317 max: ScalarValue::Utf8(Some("foobar".to_string())),
2318 null_count: 0,
2319 nan_count: 0,
2320 bound: ZoneBound {
2321 fragment_id: 4,
2322 start: 0,
2323 length: 100,
2324 },
2325 },
2326 ZoneMapStatistics {
2327 min: ScalarValue::Utf8(Some("gaa".to_string())),
2328 max: ScalarValue::Utf8(Some("gzz".to_string())),
2329 null_count: 0,
2330 nan_count: 0,
2331 bound: ZoneBound {
2332 fragment_id: 5,
2333 start: 0,
2334 length: 100,
2335 },
2336 },
2337 ];
2338
2339 let index = ZoneMapIndex {
2340 zones,
2341 data_type: DataType::Utf8,
2342 rows_per_zone: ROWS_PER_ZONE_DEFAULT,
2343 store: test_store,
2344 fri: None,
2345 index_cache: WeakLanceCache::from(&LanceCache::no_cache()),
2346 };
2347
2348 let query = SargableQuery::LikePrefix(ScalarValue::Utf8(Some("foo".to_string())));
2350 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2351
2352 let mut expected = RowAddrTreeMap::new();
2354 expected.insert_range((2u64 << 32)..((2u64 << 32) + 100));
2356 expected.insert_range((4u64 << 32)..((4u64 << 32) + 100));
2358
2359 assert_eq!(result, SearchResult::at_most(expected));
2360 }
2361
2362 #[tokio::test]
2363 async fn test_like_prefix_edge_cases() {
2364 let tmpdir = TempObjDir::default();
2365 let test_store = Arc::new(LanceIndexStore::new(
2366 Arc::new(ObjectStore::local()),
2367 tmpdir.clone(),
2368 Arc::new(LanceCache::no_cache()),
2369 ));
2370
2371 let zones = vec![
2373 ZoneMapStatistics {
2375 min: ScalarValue::Utf8(Some("test".to_string())),
2376 max: ScalarValue::Utf8(Some("test".to_string())),
2377 null_count: 0,
2378 nan_count: 0,
2379 bound: ZoneBound {
2380 fragment_id: 0,
2381 start: 0,
2382 length: 100,
2383 },
2384 },
2385 ZoneMapStatistics {
2387 min: ScalarValue::Utf8(Some("te".to_string())),
2388 max: ScalarValue::Utf8(Some("tf".to_string())),
2389 null_count: 0,
2390 nan_count: 0,
2391 bound: ZoneBound {
2392 fragment_id: 1,
2393 start: 0,
2394 length: 100,
2395 },
2396 },
2397 ZoneMapStatistics {
2399 min: ScalarValue::Utf8(Some("abc".to_string())),
2400 max: ScalarValue::Utf8(Some("def".to_string())),
2401 null_count: 0,
2402 nan_count: 0,
2403 bound: ZoneBound {
2404 fragment_id: 2,
2405 start: 0,
2406 length: 100,
2407 },
2408 },
2409 ];
2410
2411 let index = ZoneMapIndex {
2412 zones,
2413 data_type: DataType::Utf8,
2414 rows_per_zone: ROWS_PER_ZONE_DEFAULT,
2415 store: test_store,
2416 fri: None,
2417 index_cache: WeakLanceCache::from(&LanceCache::no_cache()),
2418 };
2419
2420 let query = SargableQuery::LikePrefix(ScalarValue::Utf8(Some("test".to_string())));
2422 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2423
2424 let mut expected = RowAddrTreeMap::new();
2426 expected.insert_range(0..100); expected.insert_range((1u64 << 32)..((1u64 << 32) + 100));
2428
2429 assert_eq!(result, SearchResult::at_most(expected));
2430
2431 let query = SargableQuery::LikePrefix(ScalarValue::Utf8(Some("".to_string())));
2433 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2434
2435 let mut expected = RowAddrTreeMap::new();
2436 expected.insert_range(0..100); expected.insert_range((1u64 << 32)..((1u64 << 32) + 100));
2438 expected.insert_range((2u64 << 32)..((2u64 << 32) + 100));
2439
2440 assert_eq!(result, SearchResult::at_most(expected));
2441 }
2442
2443 #[tokio::test]
2444 async fn test_like_prefix_large_utf8() {
2445 let tmpdir = TempObjDir::default();
2446 let test_store = Arc::new(LanceIndexStore::new(
2447 Arc::new(ObjectStore::local()),
2448 tmpdir.clone(),
2449 Arc::new(LanceCache::no_cache()),
2450 ));
2451
2452 let zones = vec![
2454 ZoneMapStatistics {
2455 min: ScalarValue::LargeUtf8(Some("aaa".to_string())),
2456 max: ScalarValue::LargeUtf8(Some("azz".to_string())),
2457 null_count: 0,
2458 nan_count: 0,
2459 bound: ZoneBound {
2460 fragment_id: 0,
2461 start: 0,
2462 length: 100,
2463 },
2464 },
2465 ZoneMapStatistics {
2466 min: ScalarValue::LargeUtf8(Some("foo".to_string())),
2467 max: ScalarValue::LargeUtf8(Some("foobar".to_string())),
2468 null_count: 0,
2469 nan_count: 0,
2470 bound: ZoneBound {
2471 fragment_id: 1,
2472 start: 0,
2473 length: 100,
2474 },
2475 },
2476 ];
2477
2478 let index = ZoneMapIndex {
2479 zones,
2480 data_type: DataType::LargeUtf8,
2481 rows_per_zone: ROWS_PER_ZONE_DEFAULT,
2482 store: test_store,
2483 fri: None,
2484 index_cache: WeakLanceCache::from(&LanceCache::no_cache()),
2485 };
2486
2487 let query = SargableQuery::LikePrefix(ScalarValue::LargeUtf8(Some("foo".to_string())));
2489 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2490
2491 let mut expected = RowAddrTreeMap::new();
2493 expected.insert_range((1u64 << 32)..((1u64 << 32) + 100));
2494
2495 assert_eq!(result, SearchResult::at_most(expected));
2496 }
2497
2498 #[test]
2499 fn test_compute_next_prefix() {
2500 use super::compute_next_prefix;
2501
2502 assert_eq!(compute_next_prefix("foo"), Some("fop".to_string()));
2504 assert_eq!(compute_next_prefix("abc"), Some("abd".to_string()));
2505 assert_eq!(compute_next_prefix("a"), Some("b".to_string()));
2506 assert_eq!(compute_next_prefix("z"), Some("{".to_string())); assert_eq!(compute_next_prefix("abz"), Some("ab{".to_string()));
2510
2511 assert_eq!(compute_next_prefix("ab~"), Some("ab\x7f".to_string()));
2513
2514 assert_eq!(compute_next_prefix(""), None);
2516
2517 assert_eq!(compute_next_prefix("café"), Some("cafê".to_string()));
2520 assert_eq!(compute_next_prefix("abc中"), Some("abc丮".to_string()));
2522 assert_eq!(compute_next_prefix("cafÿ"), Some("cafĀ".to_string()));
2524
2525 assert_eq!(
2528 compute_next_prefix("a\u{D7FF}"),
2529 Some("a\u{E000}".to_string())
2530 );
2531
2532 assert_eq!(compute_next_prefix("ab\u{10FFFF}"), Some("ac".to_string()));
2534 assert_eq!(compute_next_prefix("\u{10FFFF}\u{10FFFF}"), None);
2536 }
2537}