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};
24use datafusion::functions_aggregate::min_max::{MaxAccumulator, MinAccumulator};
25use datafusion_expr::Accumulator;
26use lance_core::cache::{LanceCache, WeakLanceCache};
27use serde::{Deserialize, Serialize};
28use std::sync::LazyLock;
29
30use arrow_array::{ArrayRef, RecordBatch, UInt32Array, UInt64Array, new_empty_array};
31use arrow_schema::{DataType, Field};
32use datafusion::execution::SendableRecordBatchStream;
33use datafusion_common::ScalarValue;
34use std::{collections::HashMap, sync::Arc};
35
36use super::{AnyQuery, IndexStore, MetricsCollector, ScalarIndex, SearchResult};
37use crate::scalar::FragReuseIndex;
38use crate::vector::VectorIndex;
39use crate::{Index, IndexType};
40use async_trait::async_trait;
41use deepsize::DeepSizeOf;
42use lance_core::Error;
43use lance_core::Result;
44use roaring::RoaringBitmap;
45
46use super::zoned::{ZoneBound, ZoneProcessor, ZoneTrainer, rebuild_zones, search_zones};
47const ROWS_PER_ZONE_DEFAULT: u64 = 8192; const ZONEMAP_FILENAME: &str = "zonemap.lance";
50const ZONEMAP_SIZE_META_KEY: &str = "rows_per_zone";
51const ZONEMAP_INDEX_VERSION: u32 = 0;
52
53#[derive(Debug, PartialEq, Clone)]
55struct ZoneMapStatistics {
56 min: ScalarValue,
57 max: ScalarValue,
58 null_count: u32,
59 nan_count: u32,
61 bound: ZoneBound,
64}
65
66impl DeepSizeOf for ZoneMapStatistics {
67 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
68 let min_size = self.min.size() - std::mem::size_of::<ScalarValue>();
70 let max_size = self.max.size() - std::mem::size_of::<ScalarValue>();
71
72 min_size + max_size
73 }
74}
75
76impl AsRef<ZoneBound> for ZoneMapStatistics {
77 fn as_ref(&self) -> &ZoneBound {
78 &self.bound
79 }
80}
81
82pub struct ZoneMapIndex {
104 zones: Vec<ZoneMapStatistics>,
105 data_type: DataType,
106 rows_per_zone: u64,
108 store: Arc<dyn IndexStore>,
109 fri: Option<Arc<FragReuseIndex>>,
110 index_cache: WeakLanceCache,
111}
112
113impl std::fmt::Debug for ZoneMapIndex {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("ZoneMapIndex")
116 .field("zones", &self.zones)
117 .field("data_type", &self.data_type)
118 .field("rows_per_zone", &self.rows_per_zone)
119 .field("store", &self.store)
120 .field("fri", &self.fri)
121 .field("index_cache", &self.index_cache)
122 .finish()
123 }
124}
125
126impl DeepSizeOf for ZoneMapIndex {
127 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
128 self.zones.deep_size_of_children(context)
129 }
130}
131
132impl ZoneMapIndex {
133 fn evaluate_zone_against_query(
137 &self,
138 zone: &ZoneMapStatistics,
139 query: &SargableQuery,
140 ) -> Result<bool> {
141 use std::ops::Bound;
142
143 match query {
144 SargableQuery::IsNull() => {
145 Ok(zone.null_count > 0)
147 }
148 SargableQuery::Equals(target) => {
149 if target.is_null() {
152 return Ok(zone.null_count > 0);
153 }
154
155 let is_nan = match target {
157 ScalarValue::Float16(Some(f)) => f.is_nan(),
158 ScalarValue::Float32(Some(f)) => f.is_nan(),
159 ScalarValue::Float64(Some(f)) => f.is_nan(),
160 _ => false,
161 };
162
163 if is_nan {
164 return Ok(zone.nan_count > 0);
165 }
166
167 let min_check = target >= &zone.min;
170 let max_check = match &zone.max {
171 ScalarValue::Float16(Some(f)) if f.is_nan() => true,
172 ScalarValue::Float32(Some(f)) if f.is_nan() => true,
173 ScalarValue::Float64(Some(f)) if f.is_nan() => true,
174 _ => target <= &zone.max,
175 };
176 Ok(min_check && max_check)
177 }
178 SargableQuery::Range(start, end) => {
179 let zone_min = &zone.min;
182 let zone_max = &zone.max;
183
184 let start_check = match start {
185 Bound::Unbounded => true,
186 Bound::Included(s) => {
187 match s {
189 ScalarValue::Float16(Some(f)) => {
190 if f.is_nan() {
191 return Ok(zone.nan_count > 0);
192 }
193 }
194 ScalarValue::Float32(Some(f)) => {
195 if f.is_nan() {
196 return Ok(zone.nan_count > 0);
197 }
198 }
199 ScalarValue::Float64(Some(f)) => {
200 if f.is_nan() {
201 return Ok(zone.nan_count > 0);
202 }
203 }
204 _ => {}
205 }
206 match zone_max {
210 ScalarValue::Float16(Some(f)) if f.is_nan() => true,
211 ScalarValue::Float32(Some(f)) if f.is_nan() => true,
212 ScalarValue::Float64(Some(f)) if f.is_nan() => true,
213 _ => zone_max >= s,
214 }
215 }
216 Bound::Excluded(s) => {
217 match s {
219 ScalarValue::Float16(Some(f)) => {
220 if f.is_nan() {
221 return Ok(false); }
223 }
224 ScalarValue::Float32(Some(f)) => {
225 if f.is_nan() {
226 return Ok(false); }
228 }
229 ScalarValue::Float64(Some(f)) => {
230 if f.is_nan() {
231 return Ok(false); }
233 }
234 _ => {}
235 }
236 zone_max > s
237 }
238 };
239
240 let end_check = match end {
241 Bound::Unbounded => true,
242 Bound::Included(e) => {
243 match e {
245 ScalarValue::Float16(Some(f)) => {
246 if f.is_nan() {
247 return Ok(zone.nan_count > 0 || zone_min <= e);
249 }
250 }
251 ScalarValue::Float32(Some(f)) => {
252 if f.is_nan() {
253 return Ok(zone.nan_count > 0 || zone_min <= e);
254 }
255 }
256 ScalarValue::Float64(Some(f)) => {
257 if f.is_nan() {
258 return Ok(zone.nan_count > 0 || zone_min <= e);
259 }
260 }
261 _ => {}
262 }
263 zone_min <= e
264 }
265 Bound::Excluded(e) => {
266 match e {
268 ScalarValue::Float16(Some(f)) => {
269 if f.is_nan() {
270 return Ok(true);
272 }
273 }
274 ScalarValue::Float32(Some(f)) => {
275 if f.is_nan() {
276 return Ok(true);
277 }
278 }
279 ScalarValue::Float64(Some(f)) => {
280 if f.is_nan() {
281 return Ok(true);
282 }
283 }
284 _ => {}
285 }
286 zone_min < e
287 }
288 };
289
290 Ok(start_check && end_check)
291 }
292 SargableQuery::IsIn(values) => {
293 Ok(values.iter().any(|value| {
295 if value.is_null() {
296 zone.null_count > 0
297 } else {
298 match value {
299 ScalarValue::Float16(Some(f)) => {
300 if f.is_nan() {
301 zone.nan_count > 0
302 } else {
303 value >= &zone.min && value <= &zone.max
304 }
305 }
306 ScalarValue::Float32(Some(f)) => {
307 if f.is_nan() {
308 zone.nan_count > 0
309 } else {
310 value >= &zone.min && value <= &zone.max
311 }
312 }
313 ScalarValue::Float64(Some(f)) => {
314 if f.is_nan() {
315 zone.nan_count > 0
316 } else {
317 value >= &zone.min && value <= &zone.max
318 }
319 }
320 _ => value >= &zone.min && value <= &zone.max,
321 }
322 }
323 }))
324 }
325 SargableQuery::FullTextSearch(_) => Err(Error::not_supported_source(
326 "full text search is not supported for zonemap indexes".into(),
327 )),
328 }
329 }
330
331 async fn load(
333 store: Arc<dyn IndexStore>,
334 fri: Option<Arc<FragReuseIndex>>,
335 index_cache: &LanceCache,
336 ) -> Result<Arc<Self>>
337 where
338 Self: Sized,
339 {
340 let index_file = store.open_index_file(ZONEMAP_FILENAME).await?;
341 let zone_maps = index_file
342 .read_range(0..index_file.num_rows(), None)
343 .await?;
344 let file_schema = index_file.schema();
345
346 let rows_per_zone: u64 = file_schema
347 .metadata
348 .get(ZONEMAP_SIZE_META_KEY)
349 .and_then(|bs| bs.parse().ok())
350 .unwrap_or(ROWS_PER_ZONE_DEFAULT);
351 Ok(Arc::new(Self::try_from_serialized(
352 zone_maps,
353 store,
354 fri,
355 index_cache,
356 rows_per_zone,
357 )?))
358 }
359
360 fn try_from_serialized(
361 data: RecordBatch,
362 store: Arc<dyn IndexStore>,
363 fri: Option<Arc<FragReuseIndex>>,
364 index_cache: &LanceCache,
365 rows_per_zone: u64,
366 ) -> Result<Self> {
367 let min_col = data
369 .column_by_name("min")
370 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'min' column"))?;
371 let max_col = data
372 .column_by_name("max")
373 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'max' column"))?;
374 let null_count_col = data
375 .column_by_name("null_count")
376 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'null_count' column"))?
377 .as_any()
378 .downcast_ref::<arrow_array::UInt32Array>()
379 .ok_or_else(|| {
380 Error::invalid_input("ZoneMapIndex: 'null_count' column is not UInt32")
381 })?;
382 let nan_count_col = data
383 .column_by_name("nan_count")
384 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'nan_count' column"))?
385 .as_any()
386 .downcast_ref::<arrow_array::UInt32Array>()
387 .ok_or_else(|| {
388 Error::invalid_input("ZoneMapIndex: 'nan_count' column is not UInt32")
389 })?;
390 let zone_length = data
391 .column_by_name("zone_length")
392 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'zone_length' column"))?
393 .as_any()
394 .downcast_ref::<arrow_array::UInt64Array>()
395 .ok_or_else(|| {
396 Error::invalid_input("ZoneMapIndex: 'zone_length' column is not UInt64")
397 })?;
398
399 let fragment_id_col = data
400 .column_by_name("fragment_id")
401 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'fragment_id' column"))?
402 .as_any()
403 .downcast_ref::<arrow_array::UInt64Array>()
404 .ok_or_else(|| {
405 Error::invalid_input("ZoneMapIndex: 'fragment_id' column is not UInt64")
406 })?;
407
408 let zone_start_col = data
409 .column_by_name("zone_start")
410 .ok_or_else(|| Error::invalid_input("ZoneMapIndex: missing 'zone_start' column"))?
411 .as_any()
412 .downcast_ref::<arrow_array::UInt64Array>()
413 .ok_or_else(|| {
414 Error::invalid_input("ZoneMapIndex: 'zone_start' column is not UInt64")
415 })?;
416
417 let data_type = min_col.data_type().clone();
418
419 if data.num_rows() == 0 {
420 return Ok(Self {
421 zones: Vec::new(),
422 data_type,
423 rows_per_zone,
424 store,
425 fri,
426 index_cache: WeakLanceCache::from(index_cache),
427 });
428 }
429
430 let num_zones = data.num_rows();
431 let mut zones = Vec::with_capacity(num_zones);
432
433 for i in 0..num_zones {
434 let min = ScalarValue::try_from_array(min_col, i)?;
435 let max = ScalarValue::try_from_array(max_col, i)?;
436 let null_count = null_count_col.value(i);
437 let nan_count = nan_count_col.value(i);
438 zones.push(ZoneMapStatistics {
439 min,
440 max,
441 null_count,
442 nan_count,
443 bound: ZoneBound {
444 fragment_id: fragment_id_col.value(i),
445 start: zone_start_col.value(i),
446 length: zone_length.value(i) as usize,
447 },
448 });
449 }
450
451 Ok(Self {
452 zones,
453 data_type,
454 rows_per_zone,
455 store,
456 fri,
457 index_cache: WeakLanceCache::from(index_cache),
458 })
459 }
460}
461
462#[async_trait]
463impl Index for ZoneMapIndex {
464 fn as_any(&self) -> &dyn Any {
465 self
466 }
467
468 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
469 self
470 }
471
472 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn VectorIndex>> {
473 Err(Error::invalid_input_source(
474 "ZoneMapIndex is not a vector index".into(),
475 ))
476 }
477
478 async fn prewarm(&self) -> Result<()> {
479 Ok(())
481 }
482
483 fn statistics(&self) -> Result<serde_json::Value> {
484 Ok(serde_json::json!({
485 "num_zones": self.zones.len(),
486 "rows_per_zone": self.rows_per_zone,
487 }))
488 }
489
490 fn index_type(&self) -> IndexType {
491 IndexType::ZoneMap
492 }
493
494 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
495 let mut frag_ids = RoaringBitmap::new();
496
497 for zone in &self.zones {
499 frag_ids.insert(zone.bound.fragment_id as u32);
500 }
501
502 Ok(frag_ids)
503 }
504}
505
506#[async_trait]
507impl ScalarIndex for ZoneMapIndex {
508 async fn search(
509 &self,
510 query: &dyn AnyQuery,
511 metrics: &dyn MetricsCollector,
512 ) -> Result<SearchResult> {
513 let query = query.as_any().downcast_ref::<SargableQuery>().unwrap();
514 search_zones(&self.zones, metrics, |zone| {
515 self.evaluate_zone_against_query(zone, query)
516 })
517 }
518
519 fn can_remap(&self) -> bool {
520 false
521 }
522
523 async fn remap(
525 &self,
526 _mapping: &HashMap<u64, Option<u64>>,
527 _dest_store: &dyn IndexStore,
528 ) -> Result<CreatedIndex> {
529 Err(Error::invalid_input_source(
530 "ZoneMapIndex does not support remap".into(),
531 ))
532 }
533
534 async fn update(
536 &self,
537 new_data: SendableRecordBatchStream,
538 dest_store: &dyn IndexStore,
539 _valid_old_fragments: Option<&RoaringBitmap>,
540 ) -> Result<CreatedIndex> {
541 let schema = new_data.schema();
543 let value_type = schema.field(0).data_type().clone();
544
545 let options = ZoneMapIndexBuilderParams::new(self.rows_per_zone);
546 let processor = ZoneMapProcessor::new(value_type.clone())?;
547 let trainer = ZoneTrainer::new(processor, self.rows_per_zone)?;
548 let updated_zones = rebuild_zones(&self.zones, trainer, new_data).await?;
549
550 let mut builder = ZoneMapIndexBuilder::try_new(options, self.data_type.clone())?;
552 builder.options.rows_per_zone = self.rows_per_zone;
553 builder.maps = updated_zones;
554 builder.write_index(dest_store).await?;
555
556 Ok(CreatedIndex {
557 index_details: prost_types::Any::from_msg(&pbold::ZoneMapIndexDetails::default())
558 .unwrap(),
559 index_version: ZONEMAP_INDEX_VERSION,
560 })
561 }
562
563 fn update_criteria(&self) -> UpdateCriteria {
564 UpdateCriteria::only_new_data(
565 TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
566 )
567 }
568
569 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
570 let params = serde_json::to_value(ZoneMapIndexBuilderParams::new(self.rows_per_zone))?;
571 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::ZoneMap).with_params(¶ms))
572 }
573}
574
575fn default_rows_per_zone() -> u64 {
576 *DEFAULT_ROWS_PER_ZONE
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize)]
580pub struct ZoneMapIndexBuilderParams {
581 #[serde(default = "default_rows_per_zone")]
582 rows_per_zone: u64,
583}
584
585static DEFAULT_ROWS_PER_ZONE: LazyLock<u64> = LazyLock::new(|| {
586 std::env::var("LANCE_ZONEMAP_DEFAULT_ROWS_PER_ZONE")
587 .unwrap_or_else(|_| (ROWS_PER_ZONE_DEFAULT).to_string())
588 .parse()
589 .expect("failed to parse LANCE_ZONEMAP_DEFAULT_ROWS_PER_ZONE")
590});
591
592impl Default for ZoneMapIndexBuilderParams {
593 fn default() -> Self {
594 Self {
595 rows_per_zone: *DEFAULT_ROWS_PER_ZONE,
596 }
597 }
598}
599
600impl ZoneMapIndexBuilderParams {
601 pub fn new(rows_per_zone: u64) -> Self {
602 Self { rows_per_zone }
603 }
604
605 pub fn rows_per_zone(&self) -> u64 {
606 self.rows_per_zone
607 }
608}
609
610pub struct ZoneMapIndexBuilder {
612 options: ZoneMapIndexBuilderParams,
613
614 items_type: DataType,
615 maps: Vec<ZoneMapStatistics>,
616}
617
618impl ZoneMapIndexBuilder {
619 pub fn try_new(options: ZoneMapIndexBuilderParams, items_type: DataType) -> Result<Self> {
620 Ok(Self {
621 options,
622 items_type,
623 maps: Vec::new(),
624 })
625 }
626
627 pub async fn train(&mut self, batches_source: SendableRecordBatchStream) -> Result<()> {
631 let processor = ZoneMapProcessor::new(self.items_type.clone())?;
632 let trainer = ZoneTrainer::new(processor, self.options.rows_per_zone)?;
633 self.maps = trainer.train(batches_source).await?;
634 Ok(())
635 }
636
637 fn zonemap_stats_as_batch(&self) -> Result<RecordBatch> {
638 let mins = if self.maps.is_empty() {
640 new_empty_array(&self.items_type)
641 } else {
642 ScalarValue::iter_to_array(self.maps.iter().map(|stat| stat.min.clone()))?
643 };
644 let maxs = if self.maps.is_empty() {
645 new_empty_array(&self.items_type)
646 } else {
647 ScalarValue::iter_to_array(self.maps.iter().map(|stat| stat.max.clone()))?
648 };
649 let null_counts =
650 UInt32Array::from_iter_values(self.maps.iter().map(|stat| stat.null_count));
651
652 let nan_counts = UInt32Array::from_iter_values(self.maps.iter().map(|stat| stat.nan_count));
653
654 let fragment_ids =
655 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.fragment_id));
656
657 let zone_lengths =
658 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.length as u64));
659
660 let zone_starts =
661 UInt64Array::from_iter_values(self.maps.iter().map(|stat| stat.bound.start));
662
663 let schema = Arc::new(arrow_schema::Schema::new(vec![
664 Field::new("min", self.items_type.clone(), true),
666 Field::new("max", self.items_type.clone(), true),
667 Field::new("null_count", DataType::UInt32, false),
668 Field::new("nan_count", DataType::UInt32, false),
669 Field::new("fragment_id", DataType::UInt64, false),
670 Field::new("zone_start", DataType::UInt64, false),
671 Field::new("zone_length", DataType::UInt64, false),
672 ]));
673
674 let columns: Vec<ArrayRef> = vec![
675 mins,
676 maxs,
677 Arc::new(null_counts) as ArrayRef,
678 Arc::new(nan_counts) as ArrayRef,
679 Arc::new(fragment_ids) as ArrayRef,
680 Arc::new(zone_starts) as ArrayRef,
681 Arc::new(zone_lengths) as ArrayRef,
682 ];
683 Ok(RecordBatch::try_new(schema, columns)?)
684 }
685
686 pub async fn write_index(self, index_store: &dyn IndexStore) -> Result<()> {
687 let record_batch = self.zonemap_stats_as_batch()?;
688
689 let mut file_schema = record_batch.schema().as_ref().clone();
690 file_schema.metadata.insert(
691 ZONEMAP_SIZE_META_KEY.to_string(),
692 self.options.rows_per_zone.to_string(),
693 );
694
695 let mut index_file = index_store
696 .new_index_file(ZONEMAP_FILENAME, Arc::new(file_schema))
697 .await?;
698 index_file.write_record_batch(record_batch).await?;
699 index_file.finish().await?;
700 Ok(())
701 }
702}
703
704struct ZoneMapProcessor {
707 data_type: DataType,
708 min: MinAccumulator,
709 max: MaxAccumulator,
710 null_count: u32,
711 nan_count: u32,
712}
713
714impl ZoneMapProcessor {
715 fn new(data_type: DataType) -> Result<Self> {
716 let min = MinAccumulator::try_new(&data_type)?;
717 let max = MaxAccumulator::try_new(&data_type)?;
718 Ok(Self {
719 data_type,
720 min,
721 max,
722 null_count: 0,
723 nan_count: 0,
724 })
725 }
726
727 fn count_nans(array: &ArrayRef) -> u32 {
728 match array.data_type() {
729 DataType::Float16 => {
730 let array = array
731 .as_any()
732 .downcast_ref::<arrow_array::Float16Array>()
733 .unwrap();
734 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
735 }
736 DataType::Float32 => {
737 let array = array
738 .as_any()
739 .downcast_ref::<arrow_array::Float32Array>()
740 .unwrap();
741 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
742 }
743 DataType::Float64 => {
744 let array = array
745 .as_any()
746 .downcast_ref::<arrow_array::Float64Array>()
747 .unwrap();
748 array.values().iter().filter(|&&x| x.is_nan()).count() as u32
749 }
750 _ => 0,
751 }
752 }
753}
754
755impl ZoneProcessor for ZoneMapProcessor {
756 type ZoneStatistics = ZoneMapStatistics;
757
758 fn process_chunk(&mut self, array: &ArrayRef) -> Result<()> {
759 self.null_count += array.null_count() as u32;
760 self.nan_count += Self::count_nans(array);
761 self.min.update_batch(std::slice::from_ref(array))?;
762 self.max.update_batch(std::slice::from_ref(array))?;
763 Ok(())
764 }
765
766 fn finish_zone(&mut self, bound: ZoneBound) -> Result<Self::ZoneStatistics> {
767 Ok(ZoneMapStatistics {
768 min: self.min.evaluate()?,
769 max: self.max.evaluate()?,
770 null_count: self.null_count,
771 nan_count: self.nan_count,
772 bound,
773 })
774 }
775
776 fn reset(&mut self) -> Result<()> {
777 self.min = MinAccumulator::try_new(&self.data_type)?;
778 self.max = MaxAccumulator::try_new(&self.data_type)?;
779 self.null_count = 0;
780 self.nan_count = 0;
781 Ok(())
782 }
783}
784
785#[derive(Debug, Default)]
786pub struct ZoneMapIndexPlugin;
787
788impl ZoneMapIndexPlugin {
789 async fn train_zonemap_index(
790 batches_source: SendableRecordBatchStream,
791 index_store: &dyn IndexStore,
792 options: Option<ZoneMapIndexBuilderParams>,
793 ) -> Result<()> {
794 let value_type = batches_source.schema().field(0).data_type().clone();
796
797 let mut builder = ZoneMapIndexBuilder::try_new(options.unwrap_or_default(), value_type)?;
798
799 builder.train(batches_source).await?;
800
801 builder.write_index(index_store).await?;
802 Ok(())
803 }
804}
805
806pub struct ZoneMapIndexTrainingRequest {
807 pub params: ZoneMapIndexBuilderParams,
808 pub criteria: TrainingCriteria,
809}
810
811impl ZoneMapIndexTrainingRequest {
812 pub fn new(params: ZoneMapIndexBuilderParams) -> Self {
813 Self {
814 params,
815 criteria: TrainingCriteria::new(TrainingOrdering::Addresses).with_row_addr(),
816 }
817 }
818}
819
820impl TrainingRequest for ZoneMapIndexTrainingRequest {
821 fn as_any(&self) -> &dyn std::any::Any {
822 self
823 }
824 fn criteria(&self) -> &TrainingCriteria {
825 &self.criteria
826 }
827}
828
829#[async_trait]
830impl ScalarIndexPlugin for ZoneMapIndexPlugin {
831 fn name(&self) -> &str {
832 "ZoneMap"
833 }
834
835 fn new_training_request(
836 &self,
837 params: &str,
838 field: &Field,
839 ) -> Result<Box<dyn TrainingRequest>> {
840 if field.data_type().is_nested() {
841 return Err(Error::invalid_input_source(
842 "A zone map index can only be created on a non-nested field.".into(),
843 ));
844 }
845
846 let params = serde_json::from_str::<ZoneMapIndexBuilderParams>(params)?;
847
848 Ok(Box::new(ZoneMapIndexTrainingRequest::new(params)))
849 }
850
851 fn provides_exact_answer(&self) -> bool {
852 false
853 }
854
855 fn version(&self) -> u32 {
856 ZONEMAP_INDEX_VERSION
857 }
858
859 fn new_query_parser(
860 &self,
861 index_name: String,
862 _index_details: &prost_types::Any,
863 ) -> Option<Box<dyn ScalarQueryParser>> {
864 Some(Box::new(SargableQueryParser::new(index_name, true)))
865 }
866
867 async fn train_index(
868 &self,
869 data: SendableRecordBatchStream,
870 index_store: &dyn IndexStore,
871 request: Box<dyn TrainingRequest>,
872 fragment_ids: Option<Vec<u32>>,
873 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
874 ) -> Result<CreatedIndex> {
875 if fragment_ids.is_some() {
876 return Err(Error::invalid_input_source(
877 "ZoneMap index does not support fragment training".into(),
878 ));
879 }
880
881 let request = (request as Box<dyn std::any::Any>)
882 .downcast::<ZoneMapIndexTrainingRequest>()
883 .map_err(|_| {
884 Error::invalid_input_source(
885 "must provide training request created by new_training_request".into(),
886 )
887 })?;
888 Self::train_zonemap_index(data, index_store, Some(request.params)).await?;
889 Ok(CreatedIndex {
890 index_details: prost_types::Any::from_msg(&pbold::ZoneMapIndexDetails::default())
891 .unwrap(),
892 index_version: ZONEMAP_INDEX_VERSION,
893 })
894 }
895
896 async fn load_index(
897 &self,
898 index_store: Arc<dyn IndexStore>,
899 _index_details: &prost_types::Any,
900 frag_reuse_index: Option<Arc<FragReuseIndex>>,
901 cache: &LanceCache,
902 ) -> Result<Arc<dyn ScalarIndex>> {
903 Ok(ZoneMapIndex::load(index_store, frag_reuse_index, cache).await? as Arc<dyn ScalarIndex>)
904 }
905}
906
907#[cfg(test)]
908mod tests {
909 use crate::scalar::registry::VALUE_COLUMN_NAME;
910 use crate::scalar::{IndexStore, zonemap::ROWS_PER_ZONE_DEFAULT};
911 use std::sync::Arc;
912
913 use crate::scalar::zoned::ZoneBound;
914 use crate::scalar::zonemap::{ZoneMapIndexPlugin, ZoneMapStatistics};
915 use arrow::datatypes::Float32Type;
916 use arrow_array::{Array, RecordBatch, UInt64Array, record_batch};
917 use arrow_schema::{DataType, Field, Schema};
918 use datafusion::execution::SendableRecordBatchStream;
919 use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
920 use datafusion_common::ScalarValue;
921 use futures::{StreamExt, TryStreamExt, stream};
922 use lance_core::utils::mask::NullableRowAddrSet;
923 use lance_core::utils::tempfile::TempObjDir;
924 use lance_core::{ROW_ADDR, cache::LanceCache, utils::mask::RowAddrTreeMap};
925 use lance_datafusion::datagen::DatafusionDatagenExt;
926 use lance_datagen::ArrayGeneratorExt;
927 use lance_datagen::{BatchCount, RowCount, array};
928 use lance_io::object_store::ObjectStore;
929
930 use crate::scalar::{
931 SargableQuery, ScalarIndex, SearchResult,
932 lance_format::LanceIndexStore,
933 zonemap::{
934 ZONEMAP_FILENAME, ZONEMAP_SIZE_META_KEY, ZoneMapIndex, ZoneMapIndexBuilderParams,
935 },
936 };
937
938 use crate::Index; use crate::metrics::NoOpMetricsCollector;
941 use roaring::RoaringBitmap; use std::collections::Bound;
943
944 fn add_row_addr(stream: SendableRecordBatchStream) -> SendableRecordBatchStream {
946 let schema = stream.schema();
947 let schema_with_row_addr = Arc::new(Schema::new(vec![
948 schema.field(0).clone(),
949 Field::new(ROW_ADDR, DataType::UInt64, false),
950 ]));
951 let schema = schema_with_row_addr.clone();
952 let stream = stream.enumerate().map(move |(frag_id, batch)| {
953 let batch = batch.unwrap();
954 let row_addr = Arc::new(UInt64Array::from_iter_values(
955 (0..batch.num_rows() as u64).map(|off| off + ((frag_id as u64) << 32)),
956 ));
957 Ok(RecordBatch::try_new(
958 schema_with_row_addr.clone(),
959 vec![batch.column(0).clone(), row_addr],
960 )?)
961 });
962 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
963 }
964
965 #[tokio::test]
966 async fn test_empty_zonemap_index() {
967 let tmpdir = TempObjDir::default();
968 let test_store = Arc::new(LanceIndexStore::new(
969 Arc::new(ObjectStore::local()),
970 tmpdir.clone(),
971 Arc::new(LanceCache::no_cache()),
972 ));
973
974 let data = arrow_array::Int32Array::from(Vec::<i32>::new());
975 let row_ids = arrow_array::UInt64Array::from(Vec::<u64>::new());
976 let schema = Arc::new(Schema::new(vec![
977 Field::new(VALUE_COLUMN_NAME, DataType::Int32, false),
978 Field::new(ROW_ADDR, DataType::UInt64, false),
979 ]));
980 let data =
981 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
982
983 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
984 schema,
985 stream::once(std::future::ready(Ok(data))),
986 ));
987
988 ZoneMapIndexPlugin::train_zonemap_index(data_stream, test_store.as_ref(), None)
989 .await
990 .unwrap();
991
992 log::debug!("Successfully wrote the index file");
993
994 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
996 .await
997 .expect("Failed to load ZoneMapIndex");
998 assert_eq!(index.zones.len(), 0);
999 assert_eq!(index.data_type, DataType::Int32);
1000 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
1001
1002 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1004 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1005 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1006 }
1007
1008 #[tokio::test]
1009 async fn test_null_zonemap_index() {
1011 let tmpdir = TempObjDir::default();
1012 let test_store = Arc::new(LanceIndexStore::new(
1013 Arc::new(ObjectStore::local()),
1014 tmpdir.clone(),
1015 Arc::new(LanceCache::no_cache()),
1016 ));
1017
1018 let stream = lance_datagen::gen_batch()
1019 .col(
1020 VALUE_COLUMN_NAME,
1021 array::rand::<Float32Type>().with_nulls(&[true, false, false, false, false]),
1022 )
1023 .into_df_stream(RowCount::from(5000), BatchCount::from(10));
1024
1025 let stream = add_row_addr(stream);
1027
1028 ZoneMapIndexPlugin::train_zonemap_index(
1029 stream,
1030 test_store.as_ref(),
1031 Some(ZoneMapIndexBuilderParams::new(5000)),
1032 )
1033 .await
1034 .unwrap();
1035
1036 log::debug!("Successfully wrote the index file");
1037
1038 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1040 .await
1041 .expect("Failed to load ZoneMapIndex");
1042 assert_eq!(index.zones.len(), 10);
1043 for (i, zone) in index.zones.iter().enumerate() {
1044 assert_eq!(zone.null_count, 1000);
1045 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1046 assert_eq!(zone.bound.length, 5000);
1047 assert_eq!(zone.bound.fragment_id, i as u64);
1048 }
1049
1050 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1052 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1053
1054 let mut expected = RowAddrTreeMap::new();
1056 for fragment_id in 0..10 {
1057 let start = (fragment_id as u64) << 32;
1058 let end = start + 5000;
1059 expected.insert_range(start..end);
1060 }
1061 assert_eq!(result, SearchResult::at_most(expected));
1062
1063 let new_data =
1065 arrow_array::Float32Array::from_iter_values((0..5000).map(|i| i as f32 / 1000.0));
1066 let new_row_addr =
1068 UInt64Array::from_iter_values((0..5000).map(|i| (10u64 << 32) | (i as u64)));
1069 let new_schema = Arc::new(Schema::new(vec![
1070 Field::new(VALUE_COLUMN_NAME, DataType::Float32, false), Field::new(ROW_ADDR, DataType::UInt64, false), ]));
1073 let new_data_batch = RecordBatch::try_new(
1074 new_schema.clone(),
1075 vec![Arc::new(new_data), Arc::new(new_row_addr)],
1076 )
1077 .unwrap();
1078 let new_data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1079 new_schema,
1080 stream::once(std::future::ready(Ok(new_data_batch))),
1081 ));
1082
1083 index
1086 .update(new_data_stream, test_store.as_ref(), None)
1087 .await
1088 .unwrap();
1089
1090 let updated_index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1092 .await
1093 .expect("Failed to load updated ZoneMapIndex");
1094
1095 assert_eq!(updated_index.zones.len(), 11);
1097
1098 let new_zone = &updated_index.zones[10]; assert_eq!(new_zone.bound.fragment_id, 10u64); assert_eq!(new_zone.bound.length, 5000);
1102 assert_eq!(new_zone.null_count, 0); assert_eq!(new_zone.nan_count, 0); let query = SargableQuery::Equals(ScalarValue::Float32(None));
1107 let result = updated_index
1108 .search(&query, &NoOpMetricsCollector)
1109 .await
1110 .unwrap();
1111
1112 let mut expected = RowAddrTreeMap::new();
1114 for fragment_id in 0..10 {
1115 let start = (fragment_id as u64) << 32;
1116 let end = start + 5000;
1117 expected.insert_range(start..end);
1118 }
1119 assert_eq!(result, SearchResult::at_most(expected));
1120
1121 let query = SargableQuery::Equals(ScalarValue::Float32(Some(2.5))); let result = updated_index
1124 .search(&query, &NoOpMetricsCollector)
1125 .await
1126 .unwrap();
1127
1128 let mut expected = RowAddrTreeMap::new();
1130 let start = 10u64 << 32;
1131 let end = start + 5000;
1132 expected.insert_range(start..end);
1133 assert_eq!(result, SearchResult::at_most(expected));
1134 }
1135
1136 #[tokio::test]
1137 async fn test_zonemap_null_handling_in_queries() {
1138 let tmpdir = TempObjDir::default();
1140 let store = Arc::new(LanceIndexStore::new(
1141 Arc::new(ObjectStore::local()),
1142 tmpdir.clone(),
1143 Arc::new(LanceCache::no_cache()),
1144 ));
1145
1146 let batch = record_batch!(
1148 (VALUE_COLUMN_NAME, Int64, [Some(0), Some(5), None]),
1149 (ROW_ADDR, UInt64, [0, 1, 2])
1150 )
1151 .unwrap();
1152 let schema = batch.schema();
1153 let stream = stream::once(async move { Ok(batch) });
1154 let stream = Box::pin(RecordBatchStreamAdapter::new(schema, stream));
1155
1156 ZoneMapIndexPlugin::train_zonemap_index(stream, store.as_ref(), None)
1158 .await
1159 .unwrap();
1160
1161 let cache = LanceCache::with_capacity(1024 * 1024);
1162 let index = ZoneMapIndex::load(store.clone(), None, &cache)
1163 .await
1164 .unwrap();
1165
1166 let query = SargableQuery::Equals(ScalarValue::Int64(Some(5)));
1169 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1170
1171 match result {
1172 SearchResult::AtMost(row_ids) => {
1173 let all_rows: Vec<u64> = row_ids
1176 .true_rows()
1177 .row_addrs()
1178 .unwrap()
1179 .map(u64::from)
1180 .collect();
1181 assert_eq!(
1182 all_rows,
1183 vec![0, 1, 2],
1184 "Should return all rows (including nulls) since ZoneMap is inexact"
1185 );
1186
1187 }
1190 _ => panic!("Expected AtMost search result from zonemap"),
1191 }
1192
1193 let query = SargableQuery::Range(
1195 std::ops::Bound::Included(ScalarValue::Int64(Some(0))),
1196 std::ops::Bound::Included(ScalarValue::Int64(Some(3))),
1197 );
1198 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1199
1200 match result {
1201 SearchResult::AtMost(row_ids) => {
1202 let all_rows: Vec<u64> = row_ids
1204 .true_rows()
1205 .row_addrs()
1206 .unwrap()
1207 .map(u64::from)
1208 .collect();
1209 assert_eq!(
1210 all_rows,
1211 vec![0, 1, 2],
1212 "Should return all rows in zone as possible matches"
1213 );
1214 }
1215 _ => panic!("Expected AtMost search result from zonemap"),
1216 }
1217 }
1218
1219 #[tokio::test]
1220 async fn test_nan_zonemap_index() {
1221 let tmpdir = TempObjDir::default();
1222 let test_store = Arc::new(LanceIndexStore::new(
1223 Arc::new(ObjectStore::local()),
1224 tmpdir.clone(),
1225 Arc::new(LanceCache::no_cache()),
1226 ));
1227
1228 let mut values = Vec::new();
1231 for i in 0..500 {
1232 if i % 5 == 2 {
1233 values.push(f32::NAN);
1234 } else {
1235 values.push(i as f32);
1237 }
1238 }
1239
1240 let float_data = arrow_array::Float32Array::from(values);
1241 let row_ids = UInt64Array::from_iter_values((0..float_data.len()).map(|i| i as u64));
1242 let schema = Arc::new(Schema::new(vec![
1243 Field::new(VALUE_COLUMN_NAME, DataType::Float32, true),
1244 Field::new(ROW_ADDR, DataType::UInt64, false),
1245 ]));
1246 let data = RecordBatch::try_new(
1247 schema.clone(),
1248 vec![Arc::new(float_data.clone()), Arc::new(row_ids)],
1249 )
1250 .unwrap();
1251 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1252 schema,
1253 stream::once(std::future::ready(Ok(data))),
1254 ));
1255
1256 ZoneMapIndexPlugin::train_zonemap_index(
1257 data_stream,
1258 test_store.as_ref(),
1259 Some(ZoneMapIndexBuilderParams::new(100)),
1260 )
1261 .await
1262 .unwrap();
1263
1264 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1266 .await
1267 .expect("Failed to load ZoneMapIndex");
1268
1269 assert_eq!(index.zones.len(), 5);
1271
1272 for (i, zone) in index.zones.iter().enumerate() {
1276 assert_eq!(zone.nan_count, 20, "Zone {} should have 20 NaN values", i);
1277 assert_eq!(
1278 zone.bound.length, 100,
1279 "Zone {} should have zone_length 100",
1280 i
1281 );
1282 assert_eq!(
1283 zone.bound.fragment_id, 0u64,
1284 "Zone {} should have fragment_id 0",
1285 i
1286 );
1287 }
1288
1289 let query = SargableQuery::Equals(ScalarValue::Float32(Some(f32::NAN)));
1291 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1292
1293 let mut expected = RowAddrTreeMap::new();
1295 expected.insert_range(0..500); assert_eq!(result, SearchResult::at_most(expected));
1297
1298 let query = SargableQuery::Equals(ScalarValue::Float32(Some(5.0)));
1300 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1301
1302 let mut expected = RowAddrTreeMap::new();
1304 expected.insert_range(0..100);
1305 assert_eq!(result, SearchResult::at_most(expected));
1306
1307 let query = SargableQuery::Equals(ScalarValue::Float32(Some(1000.0)));
1309 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1310
1311 let mut expected = RowAddrTreeMap::new();
1314 expected.insert_range(0..500);
1315 assert_eq!(result, SearchResult::at_most(expected));
1316
1317 let query = SargableQuery::Range(
1319 Bound::Included(ScalarValue::Float32(Some(0.0))),
1320 Bound::Included(ScalarValue::Float32(Some(250.0))),
1321 );
1322 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1323
1324 let mut expected = RowAddrTreeMap::new();
1326 expected.insert_range(0..300);
1327 assert_eq!(result, SearchResult::at_most(expected));
1328
1329 let query = SargableQuery::IsIn(vec![
1331 ScalarValue::Float32(Some(f32::NAN)),
1332 ScalarValue::Float32(Some(5.0)),
1333 ScalarValue::Float32(Some(150.0)), ]);
1335 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1336
1337 let mut expected = RowAddrTreeMap::new();
1339 expected.insert_range(0..500);
1340 assert_eq!(result, SearchResult::at_most(expected));
1341
1342 let query = SargableQuery::Range(
1344 Bound::Included(ScalarValue::Float32(Some(1000.0))),
1345 Bound::Included(ScalarValue::Float32(Some(2000.0))),
1346 );
1347 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1348
1349 let mut expected = RowAddrTreeMap::new();
1352 expected.insert_range(0..500);
1353 assert_eq!(result, SearchResult::at_most(expected));
1354
1355 let query = SargableQuery::IsNull();
1357 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1358 assert_eq!(result, SearchResult::AtMost(NullableRowAddrSet::empty()));
1359
1360 let query = SargableQuery::Range(
1363 Bound::Included(ScalarValue::Float32(Some(f32::NAN))),
1364 Bound::Unbounded,
1365 );
1366 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1367 let mut expected = RowAddrTreeMap::new();
1369 expected.insert_range(0..500);
1370 assert_eq!(result, SearchResult::at_most(expected));
1371
1372 let query = SargableQuery::Range(
1374 Bound::Unbounded,
1375 Bound::Included(ScalarValue::Float32(Some(f32::NAN))),
1376 );
1377 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1378 let mut expected = RowAddrTreeMap::new();
1380 expected.insert_range(0..500);
1381 assert_eq!(result, SearchResult::at_most(expected));
1382
1383 let query = SargableQuery::Range(
1385 Bound::Unbounded,
1386 Bound::Excluded(ScalarValue::Float32(Some(f32::NAN))),
1387 );
1388 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
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::Excluded(ScalarValue::Float32(Some(f32::NAN))),
1397 Bound::Unbounded,
1398 );
1399 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1400 assert_eq!(result, SearchResult::AtMost(NullableRowAddrSet::empty()));
1402
1403 let query = SargableQuery::IsIn(vec![
1405 ScalarValue::Float16(Some(half::f16::NAN)),
1406 ScalarValue::Float32(Some(f32::NAN)),
1407 ScalarValue::Float64(Some(f64::NAN)),
1408 ScalarValue::Float32(Some(5.0)),
1409 ]);
1410 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1411 let mut expected = RowAddrTreeMap::new();
1413 expected.insert_range(0..500);
1414 assert_eq!(result, SearchResult::at_most(expected));
1415 }
1416
1417 #[tokio::test]
1418 async fn test_basic_zonemap_index() {
1420 let tmpdir = TempObjDir::default();
1421 let test_store = Arc::new(LanceIndexStore::new(
1422 Arc::new(ObjectStore::local()),
1423 tmpdir.clone(),
1424 Arc::new(LanceCache::no_cache()),
1425 ));
1426
1427 let data = arrow_array::Int32Array::from_iter_values(0..=100);
1428 let row_ids = UInt64Array::from_iter_values((0..data.len()).map(|i| i as u64));
1429 let schema = Arc::new(Schema::new(vec![
1430 Field::new(VALUE_COLUMN_NAME, DataType::Int32, false),
1431 Field::new(ROW_ADDR, DataType::UInt64, false),
1432 ]));
1433 let data =
1434 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
1435 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1436 schema,
1437 stream::once(std::future::ready(Ok(data))),
1438 ));
1439
1440 ZoneMapIndexPlugin::train_zonemap_index(
1441 data_stream,
1442 test_store.as_ref(),
1443 Some(ZoneMapIndexBuilderParams::new(100)),
1444 )
1445 .await
1446 .unwrap();
1447
1448 log::debug!("Successfully wrote the index file");
1449
1450 let index_file = test_store.open_index_file(ZONEMAP_FILENAME).await.unwrap();
1452 let metadata = index_file.schema().metadata.clone();
1454 let record_batch = index_file
1455 .read_record_batch(0, index_file.num_rows() as u64)
1456 .await
1457 .unwrap();
1458 assert_eq!(record_batch.num_rows(), 2);
1459 assert_eq!(
1460 record_batch
1461 .column(0)
1462 .as_any()
1463 .downcast_ref::<arrow_array::Int32Array>()
1464 .unwrap()
1465 .values(),
1466 &[0, 100]
1467 );
1468 assert_eq!(
1469 record_batch
1470 .column(1)
1471 .as_any()
1472 .downcast_ref::<arrow_array::Int32Array>()
1473 .unwrap()
1474 .values(),
1475 &[99, 100]
1476 );
1477 assert_eq!(
1478 record_batch
1479 .column(2)
1480 .as_any()
1481 .downcast_ref::<arrow_array::UInt32Array>()
1482 .unwrap()
1483 .values(),
1484 &[0, 0]
1485 );
1486 assert_eq!(metadata.get(ZONEMAP_SIZE_META_KEY).unwrap(), "100");
1487
1488 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1490 .await
1491 .expect("Failed to load ZoneMapIndex");
1492 assert_eq!(index.zones.len(), 2);
1493 assert_eq!(
1494 index.zones,
1495 vec![
1496 ZoneMapStatistics {
1497 min: ScalarValue::Int32(Some(0)),
1498 max: ScalarValue::Int32(Some(99)),
1499 null_count: 0,
1500 nan_count: 0,
1501 bound: ZoneBound {
1502 fragment_id: 0,
1503 start: 0,
1504 length: 100,
1505 },
1506 },
1507 ZoneMapStatistics {
1508 min: ScalarValue::Int32(Some(100)),
1509 max: ScalarValue::Int32(Some(100)),
1510 null_count: 0,
1511 nan_count: 0,
1512 bound: ZoneBound {
1513 fragment_id: 0,
1514 start: 100,
1515 length: 1,
1516 },
1517 }
1518 ]
1519 );
1520 for (i, zone) in index.zones.iter().enumerate() {
1522 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1523 }
1524
1525 assert_eq!(index.data_type, DataType::Int32);
1526 assert_eq!(index.rows_per_zone, 100);
1527 assert_eq!(
1528 index.calculate_included_frags().await.unwrap(),
1529 RoaringBitmap::from_iter(0..1)
1530 );
1531
1532 let query = SargableQuery::Range(
1536 Bound::Excluded(ScalarValue::Int32(Some(50))),
1537 Bound::Unbounded,
1538 );
1539 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1540 assert_eq!(result, SearchResult::at_most(0..=100));
1541
1542 let query = SargableQuery::Range(
1544 Bound::Included(ScalarValue::Int32(Some(0))),
1545 Bound::Included(ScalarValue::Int32(Some(50))),
1546 );
1547 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1548 assert_eq!(result, SearchResult::at_most(0..=99));
1549
1550 let query = SargableQuery::Range(
1552 Bound::Included(ScalarValue::Int32(Some(101))),
1553 Bound::Included(ScalarValue::Int32(Some(200))),
1554 );
1555 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1556 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1558
1559 let query = SargableQuery::Range(
1561 Bound::Included(ScalarValue::Int32(Some(100))),
1562 Bound::Included(ScalarValue::Int32(Some(100))),
1563 );
1564 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1565 assert_eq!(result, SearchResult::at_most(100..=100));
1566
1567 let query = SargableQuery::Equals(ScalarValue::Int32(Some(0)));
1569 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1570 assert_eq!(result, SearchResult::at_most(0..=99));
1571
1572 let query = SargableQuery::Equals(ScalarValue::Int32(Some(100)));
1574 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1575 assert_eq!(result, SearchResult::at_most(100..=100));
1576
1577 let query = SargableQuery::Equals(ScalarValue::Int32(Some(101)));
1579 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1580 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1581
1582 let query = SargableQuery::IsNull();
1584 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1585 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1586 let query = SargableQuery::IsIn(vec![
1588 ScalarValue::Int32(Some(0)),
1589 ScalarValue::Int32(Some(100)),
1590 ScalarValue::Int32(Some(101)),
1591 ScalarValue::Int32(Some(50)),
1592 ]);
1593 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1594 assert_eq!(result, SearchResult::at_most(0..=100));
1596
1597 let query = SargableQuery::IsIn(vec![
1599 ScalarValue::Int32(Some(101)),
1600 ScalarValue::Int32(Some(102)),
1601 ]);
1602 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1603 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1604
1605 let query = SargableQuery::IsIn(vec![ScalarValue::Int32(None)]);
1607 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1608 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1609
1610 let query = SargableQuery::Equals(ScalarValue::Int32(None));
1612 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1613 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1614 }
1615
1616 #[tokio::test]
1617 async fn test_complex_zonemap_index() {
1619 let tmpdir = TempObjDir::default();
1620 let test_store = Arc::new(LanceIndexStore::new(
1621 Arc::new(ObjectStore::local()),
1622 tmpdir.clone(),
1623 Arc::new(LanceCache::no_cache()),
1624 ));
1625
1626 let data =
1628 arrow_array::Int64Array::from_iter_values(0..(ROWS_PER_ZONE_DEFAULT * 2 + 42) as i64);
1629 let row_ids = UInt64Array::from_iter_values((0..data.len()).map(|i| i as u64));
1630 let schema = Arc::new(Schema::new(vec![
1631 Field::new(VALUE_COLUMN_NAME, DataType::Int64, false),
1632 Field::new(ROW_ADDR, DataType::UInt64, false),
1633 ]));
1634 let data =
1635 RecordBatch::try_new(schema.clone(), vec![Arc::new(data), Arc::new(row_ids)]).unwrap();
1636 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1637 schema,
1638 stream::once(std::future::ready(Ok(data))),
1639 ));
1640
1641 ZoneMapIndexPlugin::train_zonemap_index(
1642 data_stream,
1643 test_store.as_ref(),
1644 Some(ZoneMapIndexBuilderParams::default()),
1645 )
1646 .await
1647 .unwrap();
1648
1649 log::debug!("Successfully wrote the index file");
1650
1651 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1653 .await
1654 .expect("Failed to load ZoneMapIndex");
1655 assert_eq!(index.zones.len(), 3);
1656 assert_eq!(
1657 index.zones,
1658 vec![
1659 ZoneMapStatistics {
1660 min: ScalarValue::Int64(Some(0)),
1661 max: ScalarValue::Int64(Some(8191)),
1662 null_count: 0,
1663 nan_count: 0,
1664 bound: ZoneBound {
1665 fragment_id: 0,
1666 start: 0,
1667 length: 8192,
1668 },
1669 },
1670 ZoneMapStatistics {
1671 min: ScalarValue::Int64(Some(8192)),
1672 max: ScalarValue::Int64(Some(16383)),
1673 null_count: 0,
1674 nan_count: 0,
1675 bound: ZoneBound {
1676 fragment_id: 0,
1677 start: 8192,
1678 length: 8192,
1679 },
1680 },
1681 ZoneMapStatistics {
1682 min: ScalarValue::Int64(Some(16384)),
1683 max: ScalarValue::Int64(Some(16425)),
1684 null_count: 0,
1685 nan_count: 0,
1686 bound: ZoneBound {
1687 fragment_id: 0,
1688 start: 16384,
1689 length: 42,
1690 },
1691 }
1692 ]
1693 );
1694 for (i, zone) in index.zones.iter().enumerate() {
1696 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1697 }
1698
1699 assert_eq!(index.data_type, DataType::Int64);
1700 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
1701 assert_eq!(
1702 index.calculate_included_frags().await.unwrap(),
1703 RoaringBitmap::from_iter(0..1)
1704 );
1705
1706 let query = SargableQuery::Equals(ScalarValue::Int64(Some(1000)));
1711 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1712 let mut expected = RowAddrTreeMap::new();
1714 expected.insert_range(0..=8191);
1715 assert_eq!(result, SearchResult::at_most(expected));
1716
1717 let query = SargableQuery::Equals(ScalarValue::Int64(Some(9000)));
1719 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1720 let mut expected = RowAddrTreeMap::new();
1722 expected.insert_range(8192..=16383);
1723 assert_eq!(result, SearchResult::at_most(expected));
1724
1725 let query = SargableQuery::Equals(ScalarValue::Int64(Some(20000)));
1727 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1728 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1729
1730 let query = SargableQuery::Range(
1732 Bound::Included(ScalarValue::Int64(Some(9000))),
1733 Bound::Included(ScalarValue::Int64(Some(16400))),
1734 );
1735 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1736 let mut expected = RowAddrTreeMap::new();
1738 expected.insert_range(8192..=16425);
1739 assert_eq!(result, SearchResult::at_most(expected));
1740 }
1741
1742 #[tokio::test]
1743 async fn test_multiple_fragments_zonemap() {
1745 let tmpdir = TempObjDir::default();
1746 let test_store = Arc::new(LanceIndexStore::new(
1747 Arc::new(ObjectStore::local()),
1748 tmpdir.clone(),
1749 Arc::new(LanceCache::no_cache()),
1750 ));
1751
1752 let schema = Arc::new(Schema::new(vec![
1753 Field::new(VALUE_COLUMN_NAME, DataType::Int64, false),
1754 Field::new(ROW_ADDR, DataType::UInt64, false),
1755 ]));
1756
1757 let fragment0_data =
1760 arrow_array::Int64Array::from_iter_values(0..ROWS_PER_ZONE_DEFAULT as i64);
1761 let fragment0_row_ids = UInt64Array::from_iter_values(0..ROWS_PER_ZONE_DEFAULT);
1762 let fragment0_batch = RecordBatch::try_new(
1763 schema.clone(),
1764 vec![Arc::new(fragment0_data), Arc::new(fragment0_row_ids)],
1765 )
1766 .unwrap();
1767
1768 let fragment1_data = arrow_array::Int64Array::from_iter_values(
1770 (ROWS_PER_ZONE_DEFAULT as i64)..((ROWS_PER_ZONE_DEFAULT * 2) as i64),
1771 );
1772 let fragment1_row_ids =
1773 UInt64Array::from_iter_values((0..ROWS_PER_ZONE_DEFAULT).map(|i| i + (1 << 32)));
1774 let fragment1_batch = RecordBatch::try_new(
1775 schema.clone(),
1776 vec![Arc::new(fragment1_data), Arc::new(fragment1_row_ids)],
1777 )
1778 .unwrap();
1779
1780 let fragment2_data = arrow_array::Int64Array::from_iter_values(
1782 ((ROWS_PER_ZONE_DEFAULT * 2) as i64)..((ROWS_PER_ZONE_DEFAULT * 2 + 42) as i64),
1783 );
1784 let fragment2_row_ids =
1785 UInt64Array::from_iter_values((0..42).map(|i| (i as u64) + (2 << 32)));
1786 let fragment2_batch = RecordBatch::try_new(
1787 schema.clone(),
1788 vec![Arc::new(fragment2_data), Arc::new(fragment2_row_ids)],
1789 )
1790 .unwrap();
1791
1792 {
1794 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
1796 schema.clone(),
1797 stream::iter(vec![
1798 Ok(fragment0_batch.clone()),
1799 Ok(fragment1_batch.clone()),
1800 Ok(fragment2_batch.clone()),
1801 ]),
1802 ));
1803 ZoneMapIndexPlugin::train_zonemap_index(
1804 data_stream,
1805 test_store.as_ref(),
1806 Some(ZoneMapIndexBuilderParams::new(5000)),
1807 )
1808 .await
1809 .unwrap();
1810
1811 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
1813 .await
1814 .expect("Failed to load ZoneMapIndex");
1815 assert_eq!(index.zones.len(), 5);
1816 assert_eq!(
1817 index.zones,
1818 vec![
1819 ZoneMapStatistics {
1820 min: ScalarValue::Int64(Some(0)),
1821 max: ScalarValue::Int64(Some(4999)),
1822 null_count: 0,
1823 nan_count: 0,
1824 bound: ZoneBound {
1825 fragment_id: 0,
1826 start: 0,
1827 length: 5000,
1828 },
1829 },
1830 ZoneMapStatistics {
1831 min: ScalarValue::Int64(Some(5000)),
1832 max: ScalarValue::Int64(Some(8191)),
1833 null_count: 0,
1834 nan_count: 0,
1835 bound: ZoneBound {
1836 fragment_id: 0,
1837 start: 5000,
1838 length: 3192,
1839 },
1840 },
1841 ZoneMapStatistics {
1842 min: ScalarValue::Int64(Some(8192)),
1843 max: ScalarValue::Int64(Some(13191)),
1844 null_count: 0,
1845 nan_count: 0,
1846 bound: ZoneBound {
1847 fragment_id: 1,
1848 start: 0,
1849 length: 5000,
1850 },
1851 },
1852 ZoneMapStatistics {
1853 min: ScalarValue::Int64(Some(13192)),
1854 max: ScalarValue::Int64(Some(16383)),
1855 null_count: 0,
1856 nan_count: 0,
1857 bound: ZoneBound {
1858 fragment_id: 1,
1859 start: 5000,
1860 length: 3192,
1861 },
1862 },
1863 ZoneMapStatistics {
1864 min: ScalarValue::Int64(Some(16384)),
1865 max: ScalarValue::Int64(Some(16425)),
1866 null_count: 0,
1867 nan_count: 0,
1868 bound: ZoneBound {
1869 fragment_id: 2,
1870 start: 0,
1871 length: 42,
1872 },
1873 }
1874 ]
1875 );
1876 for (i, zone) in index.zones.iter().enumerate() {
1878 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
1879 }
1880
1881 assert_eq!(index.data_type, DataType::Int64);
1882 assert_eq!(index.rows_per_zone, 5000);
1883 assert_eq!(
1884 index.calculate_included_frags().await.unwrap(),
1885 RoaringBitmap::from_iter(0..3)
1886 );
1887
1888 let verify_data_stream: SendableRecordBatchStream =
1890 Box::pin(RecordBatchStreamAdapter::new(
1891 schema.clone(),
1892 stream::iter(vec![
1893 Ok(fragment0_batch.clone()),
1894 Ok(fragment1_batch.clone()),
1895 Ok(fragment2_batch.clone()),
1896 ]),
1897 ));
1898 let batches: Vec<RecordBatch> = verify_data_stream.try_collect().await.unwrap();
1899
1900 assert_eq!(batches.len(), 3);
1901
1902 let fragment0_rowaddr_col = batches[0].column_by_name(ROW_ADDR).unwrap();
1904 let fragment0_rowaddrs = fragment0_rowaddr_col
1905 .as_any()
1906 .downcast_ref::<UInt64Array>()
1907 .unwrap();
1908 assert_eq!(
1909 fragment0_rowaddrs.values().len(),
1910 ROWS_PER_ZONE_DEFAULT as usize
1911 );
1912 assert_eq!(fragment0_rowaddrs.values()[0], 0);
1913 assert_eq!(
1914 fragment0_rowaddrs.values()[fragment0_rowaddrs.values().len() - 1],
1915 8191
1916 );
1917
1918 let fragment1_rowaddr_col = batches[1].column_by_name(ROW_ADDR).unwrap();
1920 let fragment1_rowaddrs = fragment1_rowaddr_col
1921 .as_any()
1922 .downcast_ref::<UInt64Array>()
1923 .unwrap();
1924 assert_eq!(
1925 fragment1_rowaddrs.values().len(),
1926 ROWS_PER_ZONE_DEFAULT as usize
1927 );
1928 assert_eq!(fragment1_rowaddrs.values()[0], 1u64 << 32); assert_eq!(
1930 fragment1_rowaddrs.values()[fragment1_rowaddrs.values().len() - 1],
1931 8191 | (1u64 << 32)
1932 );
1933
1934 let fragment2_rowaddr_col = batches[2].column_by_name(ROW_ADDR).unwrap();
1936 let fragment2_rowaddrs = fragment2_rowaddr_col
1937 .as_any()
1938 .downcast_ref::<UInt64Array>()
1939 .unwrap();
1940 assert_eq!(fragment2_rowaddrs.values().len(), 42);
1941 assert_eq!(fragment2_rowaddrs.values()[0], 2u64 << 32); assert_eq!(
1943 fragment2_rowaddrs.values()[fragment2_rowaddrs.values().len() - 1],
1944 (2u64 << 32) | 41
1945 );
1946
1947 let query = SargableQuery::Range(
1951 Bound::Included(ScalarValue::Int64(Some(5000))),
1952 Bound::Included(ScalarValue::Int64(Some(12000))),
1953 );
1954 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1955 let mut expected = RowAddrTreeMap::new();
1957 expected.insert_range(5000..8192);
1959 expected.insert_range((1u64 << 32)..((1u64 << 32) + 5000));
1961 assert_eq!(result, SearchResult::at_most(expected));
1962
1963 let query = SargableQuery::Equals(ScalarValue::Int64(Some(8192)));
1965 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1966 let mut expected = RowAddrTreeMap::new();
1968 expected.insert_range((1u64 << 32)..((1u64 << 32) + 5000));
1969 assert_eq!(result, SearchResult::at_most(expected));
1970
1971 let query = SargableQuery::Equals(ScalarValue::Int64(Some(16385)));
1973 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1974 let mut expected = RowAddrTreeMap::new();
1976 expected.insert_range(2u64 << 32..((2u64 << 32) + 42));
1977 assert_eq!(result, SearchResult::at_most(expected));
1978
1979 let query = SargableQuery::Equals(ScalarValue::Int64(Some(99999)));
1981 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1982 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1983
1984 let query = SargableQuery::IsIn(vec![ScalarValue::Int64(Some(16385))]);
1986 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1987 let mut expected = RowAddrTreeMap::new();
1988 expected.insert_range(2u64 << 32..((2u64 << 32) + 42));
1989 assert_eq!(result, SearchResult::at_most(expected));
1990
1991 let query = SargableQuery::Equals(ScalarValue::Int64(None));
1993 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
1994 let mut expected = RowAddrTreeMap::new();
1995 expected.insert_range(0..=16425);
1996 assert_eq!(result, SearchResult::at_most(RowAddrTreeMap::new()));
1998 }
1999
2000 {
2002 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2004 schema.clone(),
2005 stream::iter(vec![
2006 Ok(fragment0_batch.clone()),
2007 Ok(fragment1_batch.clone()),
2008 Ok(fragment2_batch.clone()),
2009 ]),
2010 ));
2011 ZoneMapIndexPlugin::train_zonemap_index(
2012 data_stream,
2013 test_store.as_ref(),
2014 Some(ZoneMapIndexBuilderParams::default()),
2015 )
2016 .await
2017 .unwrap();
2018
2019 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2021 .await
2022 .expect("Failed to load ZoneMapIndex");
2023 assert_eq!(index.zones.len(), 3);
2024 assert_eq!(
2025 index.zones,
2026 vec![
2027 ZoneMapStatistics {
2028 min: ScalarValue::Int64(Some(0)),
2029 max: ScalarValue::Int64(Some(8191)),
2030 null_count: 0,
2031 nan_count: 0,
2032 bound: ZoneBound {
2033 fragment_id: 0,
2034 start: 0,
2035 length: 8192,
2036 },
2037 },
2038 ZoneMapStatistics {
2039 min: ScalarValue::Int64(Some(8192)),
2040 max: ScalarValue::Int64(Some(16383)),
2041 null_count: 0,
2042 nan_count: 0,
2043 bound: ZoneBound {
2044 fragment_id: 1,
2045 start: 0,
2046 length: 8192,
2047 },
2048 },
2049 ZoneMapStatistics {
2050 min: ScalarValue::Int64(Some(16384)),
2051 max: ScalarValue::Int64(Some(16425)),
2052 null_count: 0,
2053 nan_count: 0,
2054 bound: ZoneBound {
2055 fragment_id: 2,
2056 start: 0,
2057 length: 42,
2058 },
2059 }
2060 ]
2061 );
2062 for (i, zone) in index.zones.iter().enumerate() {
2064 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
2065 }
2066
2067 assert_eq!(index.data_type, DataType::Int64);
2068 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT);
2069 assert_eq!(
2070 index.calculate_included_frags().await.unwrap(),
2071 RoaringBitmap::from_iter(0..3)
2072 );
2073 }
2074
2075 {
2077 let data_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2079 schema.clone(),
2080 stream::iter(vec![
2081 Ok(fragment0_batch.clone()),
2082 Ok(fragment1_batch.clone()),
2083 Ok(fragment2_batch.clone()),
2084 ]),
2085 ));
2086 ZoneMapIndexPlugin::train_zonemap_index(
2087 data_stream,
2088 test_store.as_ref(),
2089 Some(ZoneMapIndexBuilderParams::new(ROWS_PER_ZONE_DEFAULT * 3)),
2090 )
2091 .await
2092 .unwrap();
2093
2094 let index = ZoneMapIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2096 .await
2097 .expect("Failed to load ZoneMapIndex");
2098 assert_eq!(index.zones.len(), 3);
2099 assert_eq!(
2100 index.zones,
2101 vec![
2102 ZoneMapStatistics {
2103 min: ScalarValue::Int64(Some(0)),
2104 max: ScalarValue::Int64(Some(8191)),
2105 null_count: 0,
2106 nan_count: 0,
2107 bound: ZoneBound {
2108 fragment_id: 0,
2109 start: 0,
2110 length: 8192,
2111 },
2112 },
2113 ZoneMapStatistics {
2114 min: ScalarValue::Int64(Some(8192)),
2115 max: ScalarValue::Int64(Some(16383)),
2116 null_count: 0,
2117 nan_count: 0,
2118 bound: ZoneBound {
2119 fragment_id: 1,
2120 start: 0,
2121 length: 8192,
2122 },
2123 },
2124 ZoneMapStatistics {
2125 min: ScalarValue::Int64(Some(16384)),
2126 max: ScalarValue::Int64(Some(16425)),
2127 null_count: 0,
2128 nan_count: 0,
2129 bound: ZoneBound {
2130 fragment_id: 2,
2131 start: 0,
2132 length: 42,
2133 },
2134 }
2135 ]
2136 );
2137 for (i, zone) in index.zones.iter().enumerate() {
2139 assert_eq!(zone.nan_count, 0, "Zone {} should have nan_count = 0", i);
2140 }
2141
2142 assert_eq!(index.data_type, DataType::Int64);
2143 assert_eq!(index.rows_per_zone, ROWS_PER_ZONE_DEFAULT * 3);
2144 }
2145 }
2146
2147 #[tokio::test]
2148 async fn test_fragment_id_assignment() {
2149 let schema = Arc::new(Schema::new(vec![Field::new(
2151 VALUE_COLUMN_NAME,
2152 DataType::Int32,
2153 false,
2154 )]));
2155
2156 let fragment0_data = arrow_array::Int32Array::from_iter_values(0..5);
2158 let fragment0_batch =
2159 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment0_data)]).unwrap();
2160
2161 let fragment1_data = arrow_array::Int32Array::from_iter_values(5..10);
2162 let fragment1_batch =
2163 RecordBatch::try_new(schema.clone(), vec![Arc::new(fragment1_data)]).unwrap();
2164
2165 let aligned_stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
2166 schema,
2167 stream::iter(vec![Ok(fragment0_batch), Ok(fragment1_batch)]),
2168 ));
2169
2170 let aligned_stream = add_row_addr(aligned_stream);
2171
2172 let batches: Vec<RecordBatch> = aligned_stream.try_collect().await.unwrap();
2173
2174 assert_eq!(batches.len(), 2);
2175
2176 let fragment0_rowaddr_col = batches[0].column_by_name(ROW_ADDR).unwrap();
2178 let fragment0_rowaddrs = fragment0_rowaddr_col
2179 .as_any()
2180 .downcast_ref::<UInt64Array>()
2181 .unwrap();
2182
2183 assert_eq!(fragment0_rowaddrs.values(), &[0, 1, 2, 3, 4]);
2185
2186 let fragment1_rowaddr_col = batches[1].column_by_name(ROW_ADDR).unwrap();
2188 let fragment1_rowaddrs = fragment1_rowaddr_col
2189 .as_any()
2190 .downcast_ref::<UInt64Array>()
2191 .unwrap();
2192
2193 assert_eq!(
2196 fragment1_rowaddrs.values(),
2197 &[4294967296, 4294967297, 4294967298, 4294967299, 4294967300]
2198 );
2199 }
2200}