1use std::{
5 any::Any,
6 cmp::Ordering,
7 collections::{BTreeMap, BinaryHeap, HashMap, HashSet},
8 fmt::{Debug, Display},
9 ops::Bound,
10 sync::Arc,
11};
12
13use super::{
14 AnyQuery, BuiltinIndexType, IndexReader, IndexStore, IndexWriter, MetricsCollector,
15 SargableQuery, ScalarIndex, ScalarIndexParams, SearchResult,
16};
17use crate::{Index, IndexType};
18use crate::{
19 frag_reuse::FragReuseIndex,
20 scalar::{
21 CreatedIndex, UpdateCriteria,
22 expression::{SargableQueryParser, ScalarQueryParser},
23 registry::{ScalarIndexPlugin, TrainingOrdering, TrainingRequest, VALUE_COLUMN_NAME},
24 },
25};
26use crate::{metrics::NoOpMetricsCollector, scalar::registry::TrainingCriteria};
27use crate::{pbold, scalar::btree::flat::FlatIndex};
28use arrow_arith::numeric::add;
29use arrow_array::{Array, RecordBatch, UInt32Array, new_empty_array};
30use arrow_schema::{DataType, Field, Schema, SortOptions};
31use async_trait::async_trait;
32use datafusion::physical_plan::{
33 ExecutionPlan, SendableRecordBatchStream,
34 sorts::sort_preserving_merge::SortPreservingMergeExec, stream::RecordBatchStreamAdapter,
35 union::UnionExec,
36};
37use datafusion_common::{DataFusionError, ScalarValue};
38use datafusion_physical_expr::{PhysicalSortExpr, expressions::Column};
39use deepsize::DeepSizeOf;
40use futures::{
41 FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt,
42 future::BoxFuture,
43 stream::{self},
44};
45use lance_core::{
46 Error, ROW_ID, Result,
47 cache::{CacheKey, LanceCache, WeakLanceCache},
48 error::LanceOptionExt,
49 utils::{
50 mask::NullableRowAddrSet,
51 tokio::get_num_compute_intensive_cpus,
52 tracing::{IO_TYPE_LOAD_SCALAR_PART, TRACE_IO_EVENTS},
53 },
54};
55use lance_datafusion::{
56 chunker::chunk_concat_stream,
57 exec::{LanceExecutionOptions, OneShotExec, execute_plan},
58};
59use lance_io::object_store::ObjectStore;
60use log::{debug, warn};
61use object_store::path::Path;
62use rangemap::RangeInclusiveMap;
63use roaring::RoaringBitmap;
64use serde::{Deserialize, Serialize, Serializer};
65use tracing::{info, instrument};
66
67mod flat;
68
69const BTREE_LOOKUP_NAME: &str = "page_lookup.lance";
70const BTREE_PAGES_NAME: &str = "page_data.lance";
71pub const DEFAULT_BTREE_BATCH_SIZE: u64 = 4096;
72const BATCH_SIZE_META_KEY: &str = "batch_size";
73const DEFAULT_RANGE_PARTITIONED: bool = false;
74const RANGE_PARTITIONED_META_KEY: &str = "range_partitioned";
75const PAGE_NUM_PER_RANGE_PARTITION_META_KEY: &str = "page_num_per_range_partition";
76const BTREE_INDEX_VERSION: u32 = 0;
77pub(crate) const BTREE_VALUES_COLUMN: &str = "values";
78pub(crate) const BTREE_IDS_COLUMN: &str = "ids";
79
80#[derive(Clone, Debug)]
82pub struct OrderableScalarValue(pub ScalarValue);
83
84impl DeepSizeOf for OrderableScalarValue {
85 fn deep_size_of_children(&self, _context: &mut deepsize::Context) -> usize {
86 self.0.size() - std::mem::size_of::<ScalarValue>()
88 }
89}
90
91impl Display for OrderableScalarValue {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 std::fmt::Display::fmt(&self.0, f)
94 }
95}
96
97impl PartialEq for OrderableScalarValue {
98 fn eq(&self, other: &Self) -> bool {
99 self.0.eq(&other.0)
100 }
101}
102
103impl Eq for OrderableScalarValue {}
104
105impl PartialOrd for OrderableScalarValue {
106 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
107 Some(self.cmp(other))
108 }
109}
110
111impl Ord for OrderableScalarValue {
117 fn cmp(&self, other: &Self) -> Ordering {
118 use ScalarValue::*;
119 match (&self.0, &other.0) {
123 (Decimal32(v1, p1, s1), Decimal32(v2, p2, s2)) => {
124 if p1.eq(p2) && s1.eq(s2) {
125 v1.cmp(v2)
126 } else {
127 panic!("Attempt to compare decimals with unequal precision / scale")
129 }
130 }
131 (Decimal32(v1, _, _), Null) => {
132 if v1.is_none() {
133 Ordering::Equal
134 } else {
135 Ordering::Greater
136 }
137 }
138 (Decimal32(_, _, _), _) => panic!("Attempt to compare decimal with non-decimal"),
139 (Decimal64(v1, p1, s1), Decimal64(v2, p2, s2)) => {
140 if p1.eq(p2) && s1.eq(s2) {
141 v1.cmp(v2)
142 } else {
143 panic!("Attempt to compare decimals with unequal precision / scale")
145 }
146 }
147 (Decimal64(v1, _, _), Null) => {
148 if v1.is_none() {
149 Ordering::Equal
150 } else {
151 Ordering::Greater
152 }
153 }
154 (Decimal64(_, _, _), _) => panic!("Attempt to compare decimal with non-decimal"),
155 (Decimal128(v1, p1, s1), Decimal128(v2, p2, s2)) => {
156 if p1.eq(p2) && s1.eq(s2) {
157 v1.cmp(v2)
158 } else {
159 panic!("Attempt to compare decimals with unequal precision / scale")
161 }
162 }
163 (Decimal128(v1, _, _), Null) => {
164 if v1.is_none() {
165 Ordering::Equal
166 } else {
167 Ordering::Greater
168 }
169 }
170 (Decimal128(_, _, _), _) => panic!("Attempt to compare decimal with non-decimal"),
171 (Decimal256(v1, p1, s1), Decimal256(v2, p2, s2)) => {
172 if p1.eq(p2) && s1.eq(s2) {
173 v1.cmp(v2)
174 } else {
175 panic!("Attempt to compare decimals with unequal precision / scale")
177 }
178 }
179 (Decimal256(v1, _, _), Null) => {
180 if v1.is_none() {
181 Ordering::Equal
182 } else {
183 Ordering::Greater
184 }
185 }
186 (Decimal256(_, _, _), _) => panic!("Attempt to compare decimal with non-decimal"),
187
188 (Boolean(v1), Boolean(v2)) => v1.cmp(v2),
189 (Boolean(v1), Null) => {
190 if v1.is_none() {
191 Ordering::Equal
192 } else {
193 Ordering::Greater
194 }
195 }
196 (Boolean(_), _) => panic!("Attempt to compare boolean with non-boolean"),
197 (Float32(v1), Float32(v2)) => match (v1, v2) {
198 (Some(f1), Some(f2)) => f1.total_cmp(f2),
199 (None, Some(_)) => Ordering::Less,
200 (Some(_), None) => Ordering::Greater,
201 (None, None) => Ordering::Equal,
202 },
203 (Float32(v1), Null) => {
204 if v1.is_none() {
205 Ordering::Equal
206 } else {
207 Ordering::Greater
208 }
209 }
210 (Float32(_), _) => panic!("Attempt to compare f32 with non-f32"),
211 (Float64(v1), Float64(v2)) => match (v1, v2) {
212 (Some(f1), Some(f2)) => f1.total_cmp(f2),
213 (None, Some(_)) => Ordering::Less,
214 (Some(_), None) => Ordering::Greater,
215 (None, None) => Ordering::Equal,
216 },
217 (Float64(v1), Null) => {
218 if v1.is_none() {
219 Ordering::Equal
220 } else {
221 Ordering::Greater
222 }
223 }
224 (Float64(_), _) => panic!("Attempt to compare f64 with non-f64"),
225 (Float16(v1), Float16(v2)) => match (v1, v2) {
226 (Some(f1), Some(f2)) => f1.total_cmp(f2),
227 (None, Some(_)) => Ordering::Less,
228 (Some(_), None) => Ordering::Greater,
229 (None, None) => Ordering::Equal,
230 },
231 (Float16(v1), Null) => {
232 if v1.is_none() {
233 Ordering::Equal
234 } else {
235 Ordering::Greater
236 }
237 }
238 (Float16(_), _) => panic!("Attempt to compare f16 with non-f16"),
239 (Int8(v1), Int8(v2)) => v1.cmp(v2),
240 (Int8(v1), Null) => {
241 if v1.is_none() {
242 Ordering::Equal
243 } else {
244 Ordering::Greater
245 }
246 }
247 (Int8(_), _) => panic!("Attempt to compare Int8 with non-Int8"),
248 (Int16(v1), Int16(v2)) => v1.cmp(v2),
249 (Int16(v1), Null) => {
250 if v1.is_none() {
251 Ordering::Equal
252 } else {
253 Ordering::Greater
254 }
255 }
256 (Int16(_), _) => panic!("Attempt to compare Int16 with non-Int16"),
257 (Int32(v1), Int32(v2)) => v1.cmp(v2),
258 (Int32(v1), Null) => {
259 if v1.is_none() {
260 Ordering::Equal
261 } else {
262 Ordering::Greater
263 }
264 }
265 (Int32(_), _) => panic!("Attempt to compare Int32 with non-Int32"),
266 (Int64(v1), Int64(v2)) => v1.cmp(v2),
267 (Int64(v1), Null) => {
268 if v1.is_none() {
269 Ordering::Equal
270 } else {
271 Ordering::Greater
272 }
273 }
274 (Int64(_), _) => panic!("Attempt to compare Int64 with non-Int64"),
275 (UInt8(v1), UInt8(v2)) => v1.cmp(v2),
276 (UInt8(v1), Null) => {
277 if v1.is_none() {
278 Ordering::Equal
279 } else {
280 Ordering::Greater
281 }
282 }
283 (UInt8(_), _) => panic!("Attempt to compare UInt8 with non-UInt8"),
284 (UInt16(v1), UInt16(v2)) => v1.cmp(v2),
285 (UInt16(v1), Null) => {
286 if v1.is_none() {
287 Ordering::Equal
288 } else {
289 Ordering::Greater
290 }
291 }
292 (UInt16(_), _) => panic!("Attempt to compare UInt16 with non-UInt16"),
293 (UInt32(v1), UInt32(v2)) => v1.cmp(v2),
294 (UInt32(v1), Null) => {
295 if v1.is_none() {
296 Ordering::Equal
297 } else {
298 Ordering::Greater
299 }
300 }
301 (UInt32(_), _) => panic!("Attempt to compare UInt32 with non-UInt32"),
302 (UInt64(v1), UInt64(v2)) => v1.cmp(v2),
303 (UInt64(v1), Null) => {
304 if v1.is_none() {
305 Ordering::Equal
306 } else {
307 Ordering::Greater
308 }
309 }
310 (UInt64(_), _) => panic!("Attempt to compare UInt64 with non-UInt64"),
311 (Utf8(v1) | Utf8View(v1) | LargeUtf8(v1), Utf8(v2) | Utf8View(v2) | LargeUtf8(v2)) => {
312 v1.cmp(v2)
313 }
314 (Utf8(v1) | Utf8View(v1) | LargeUtf8(v1), Null) => {
315 if v1.is_none() {
316 Ordering::Equal
317 } else {
318 Ordering::Greater
319 }
320 }
321 (Utf8(_) | Utf8View(_) | LargeUtf8(_), _) => {
322 panic!("Attempt to compare Utf8 with non-Utf8")
323 }
324 (
325 Binary(v1) | LargeBinary(v1) | BinaryView(v1),
326 Binary(v2) | LargeBinary(v2) | BinaryView(v2),
327 ) => v1.cmp(v2),
328 (Binary(v1) | LargeBinary(v1) | BinaryView(v1), Null) => {
329 if v1.is_none() {
330 Ordering::Equal
331 } else {
332 Ordering::Greater
333 }
334 }
335 (Binary(_) | LargeBinary(_) | BinaryView(_), _) => {
336 panic!("Attempt to compare Binary with non-Binary")
337 }
338 (FixedSizeBinary(_, v1), FixedSizeBinary(_, v2)) => v1.cmp(v2),
339 (FixedSizeBinary(_, v1), Null) => {
340 if v1.is_none() {
341 Ordering::Equal
342 } else {
343 Ordering::Greater
344 }
345 }
346 (FixedSizeBinary(_, _), _) => {
347 panic!("Attempt to compare FixedSizeBinary with non-FixedSizeBinary")
348 }
349 (FixedSizeList(left), FixedSizeList(right)) => {
350 if left.eq(right) {
351 todo!()
352 } else {
353 panic!(
354 "Attempt to compare fixed size list elements with different widths/fields"
355 )
356 }
357 }
358 (FixedSizeList(left), Null) => {
359 if left.is_null(0) {
360 Ordering::Equal
361 } else {
362 Ordering::Greater
363 }
364 }
365 (FixedSizeList(_), _) => {
366 panic!("Attempt to compare FixedSizeList with non-FixedSizeList")
367 }
368 (List(_), List(_)) => todo!(),
369 (List(left), Null) => {
370 if left.is_null(0) {
371 Ordering::Equal
372 } else {
373 Ordering::Greater
374 }
375 }
376 (List(_), _) => {
377 panic!("Attempt to compare List with non-List")
378 }
379 (LargeList(_), _) => todo!(),
380 (Map(_), Map(_)) => todo!(),
381 (Map(left), Null) => {
382 if left.is_null(0) {
383 Ordering::Equal
384 } else {
385 Ordering::Greater
386 }
387 }
388 (Map(_), _) => {
389 panic!("Attempt to compare Map with non-Map")
390 }
391 (Date32(v1), Date32(v2)) => v1.cmp(v2),
392 (Date32(v1), Null) => {
393 if v1.is_none() {
394 Ordering::Equal
395 } else {
396 Ordering::Greater
397 }
398 }
399 (Date32(_), _) => panic!("Attempt to compare Date32 with non-Date32"),
400 (Date64(v1), Date64(v2)) => v1.cmp(v2),
401 (Date64(v1), Null) => {
402 if v1.is_none() {
403 Ordering::Equal
404 } else {
405 Ordering::Greater
406 }
407 }
408 (Date64(_), _) => panic!("Attempt to compare Date64 with non-Date64"),
409 (Time32Second(v1), Time32Second(v2)) => v1.cmp(v2),
410 (Time32Second(v1), Null) => {
411 if v1.is_none() {
412 Ordering::Equal
413 } else {
414 Ordering::Greater
415 }
416 }
417 (Time32Second(_), _) => panic!("Attempt to compare Time32Second with non-Time32Second"),
418 (Time32Millisecond(v1), Time32Millisecond(v2)) => v1.cmp(v2),
419 (Time32Millisecond(v1), Null) => {
420 if v1.is_none() {
421 Ordering::Equal
422 } else {
423 Ordering::Greater
424 }
425 }
426 (Time32Millisecond(_), _) => {
427 panic!("Attempt to compare Time32Millisecond with non-Time32Millisecond")
428 }
429 (Time64Microsecond(v1), Time64Microsecond(v2)) => v1.cmp(v2),
430 (Time64Microsecond(v1), Null) => {
431 if v1.is_none() {
432 Ordering::Equal
433 } else {
434 Ordering::Greater
435 }
436 }
437 (Time64Microsecond(_), _) => {
438 panic!("Attempt to compare Time64Microsecond with non-Time64Microsecond")
439 }
440 (Time64Nanosecond(v1), Time64Nanosecond(v2)) => v1.cmp(v2),
441 (Time64Nanosecond(v1), Null) => {
442 if v1.is_none() {
443 Ordering::Equal
444 } else {
445 Ordering::Greater
446 }
447 }
448 (Time64Nanosecond(_), _) => {
449 panic!("Attempt to compare Time64Nanosecond with non-Time64Nanosecond")
450 }
451 (TimestampSecond(v1, _), TimestampSecond(v2, _)) => v1.cmp(v2),
452 (TimestampSecond(v1, _), Null) => {
453 if v1.is_none() {
454 Ordering::Equal
455 } else {
456 Ordering::Greater
457 }
458 }
459 (TimestampSecond(_, _), _) => {
460 panic!("Attempt to compare TimestampSecond with non-TimestampSecond")
461 }
462 (TimestampMillisecond(v1, _), TimestampMillisecond(v2, _)) => v1.cmp(v2),
463 (TimestampMillisecond(v1, _), Null) => {
464 if v1.is_none() {
465 Ordering::Equal
466 } else {
467 Ordering::Greater
468 }
469 }
470 (TimestampMillisecond(_, _), _) => {
471 panic!("Attempt to compare TimestampMillisecond with non-TimestampMillisecond")
472 }
473 (TimestampMicrosecond(v1, _), TimestampMicrosecond(v2, _)) => v1.cmp(v2),
474 (TimestampMicrosecond(v1, _), Null) => {
475 if v1.is_none() {
476 Ordering::Equal
477 } else {
478 Ordering::Greater
479 }
480 }
481 (TimestampMicrosecond(_, _), _) => {
482 panic!("Attempt to compare TimestampMicrosecond with non-TimestampMicrosecond")
483 }
484 (TimestampNanosecond(v1, _), TimestampNanosecond(v2, _)) => v1.cmp(v2),
485 (TimestampNanosecond(v1, _), Null) => {
486 if v1.is_none() {
487 Ordering::Equal
488 } else {
489 Ordering::Greater
490 }
491 }
492 (TimestampNanosecond(_, _), _) => {
493 panic!("Attempt to compare TimestampNanosecond with non-TimestampNanosecond")
494 }
495 (IntervalYearMonth(v1), IntervalYearMonth(v2)) => v1.cmp(v2),
496 (IntervalYearMonth(v1), Null) => {
497 if v1.is_none() {
498 Ordering::Equal
499 } else {
500 Ordering::Greater
501 }
502 }
503 (IntervalYearMonth(_), _) => {
504 panic!("Attempt to compare IntervalYearMonth with non-IntervalYearMonth")
505 }
506 (IntervalDayTime(v1), IntervalDayTime(v2)) => v1.cmp(v2),
507 (IntervalDayTime(v1), Null) => {
508 if v1.is_none() {
509 Ordering::Equal
510 } else {
511 Ordering::Greater
512 }
513 }
514 (IntervalDayTime(_), _) => {
515 panic!("Attempt to compare IntervalDayTime with non-IntervalDayTime")
516 }
517 (IntervalMonthDayNano(v1), IntervalMonthDayNano(v2)) => v1.cmp(v2),
518 (IntervalMonthDayNano(v1), Null) => {
519 if v1.is_none() {
520 Ordering::Equal
521 } else {
522 Ordering::Greater
523 }
524 }
525 (IntervalMonthDayNano(_), _) => {
526 panic!("Attempt to compare IntervalMonthDayNano with non-IntervalMonthDayNano")
527 }
528 (DurationSecond(v1), DurationSecond(v2)) => v1.cmp(v2),
529 (DurationSecond(v1), Null) => {
530 if v1.is_none() {
531 Ordering::Equal
532 } else {
533 Ordering::Greater
534 }
535 }
536 (DurationSecond(_), _) => {
537 panic!("Attempt to compare DurationSecond with non-DurationSecond")
538 }
539 (DurationMillisecond(v1), DurationMillisecond(v2)) => v1.cmp(v2),
540 (DurationMillisecond(v1), Null) => {
541 if v1.is_none() {
542 Ordering::Equal
543 } else {
544 Ordering::Greater
545 }
546 }
547 (DurationMillisecond(_), _) => {
548 panic!("Attempt to compare DurationMillisecond with non-DurationMillisecond")
549 }
550 (DurationMicrosecond(v1), DurationMicrosecond(v2)) => v1.cmp(v2),
551 (DurationMicrosecond(v1), Null) => {
552 if v1.is_none() {
553 Ordering::Equal
554 } else {
555 Ordering::Greater
556 }
557 }
558 (DurationMicrosecond(_), _) => {
559 panic!("Attempt to compare DurationMicrosecond with non-DurationMicrosecond")
560 }
561 (DurationNanosecond(v1), DurationNanosecond(v2)) => v1.cmp(v2),
562 (DurationNanosecond(v1), Null) => {
563 if v1.is_none() {
564 Ordering::Equal
565 } else {
566 Ordering::Greater
567 }
568 }
569 (DurationNanosecond(_), _) => {
570 panic!("Attempt to compare DurationNanosecond with non-DurationNanosecond")
571 }
572 (Struct(_arr), Struct(_arr2)) => todo!(),
573 (Struct(arr), Null) => {
574 if arr.is_empty() {
575 Ordering::Equal
576 } else {
577 Ordering::Greater
578 }
579 }
580 (Struct(_arr), _) => panic!("Attempt to compare Struct with non-Struct"),
581 (Dictionary(_k1, _v1), Dictionary(_k2, _v2)) => todo!(),
582 (Dictionary(_, v1), Null) => Self(*v1.clone()).cmp(&Self(ScalarValue::Null)),
583 (Dictionary(_, _), _) => panic!("Attempt to compare Dictionary with non-Dictionary"),
584 (Union(_, _, _), _) => todo!("Support for union scalars"),
586 (Null, Null) => Ordering::Equal,
587 (Null, _) => todo!(),
588 }
589 }
590}
591
592#[derive(Debug, DeepSizeOf, PartialEq, Eq)]
593struct PageRecord {
594 max: OrderableScalarValue,
595 page_number: u32,
596}
597
598trait BTreeMapExt<K, V> {
599 fn largest_node_less(&self, key: &K) -> Option<(&K, &V)>;
600}
601
602impl<K: Ord, V> BTreeMapExt<K, V> for BTreeMap<K, V> {
603 fn largest_node_less(&self, key: &K) -> Option<(&K, &V)> {
604 self.range((Bound::Unbounded, Bound::Excluded(key)))
605 .next_back()
606 }
607}
608
609#[derive(Debug, DeepSizeOf, PartialEq, Eq)]
611pub struct BTreeLookup {
612 tree: BTreeMap<OrderableScalarValue, Vec<PageRecord>>,
613 null_pages: Vec<u32>,
615 all_null_pages: Vec<u32>,
617}
618
619impl BTreeLookup {
620 fn empty() -> Self {
621 Self {
622 tree: BTreeMap::new(),
623 null_pages: Vec::new(),
624 all_null_pages: Vec::new(),
625 }
626 }
627}
628
629#[derive(Debug, Copy, Clone)]
630enum Matches {
631 Some(u32),
632 All(u32),
633}
634
635impl Matches {
636 fn page_id(&self) -> u32 {
637 match self {
638 Self::Some(page_id) => *page_id,
639 Self::All(page_id) => *page_id,
640 }
641 }
642}
643
644impl BTreeLookup {
645 fn new(
646 tree: BTreeMap<OrderableScalarValue, Vec<PageRecord>>,
647 null_pages: Vec<u32>,
648 all_null_pages: Vec<u32>,
649 ) -> Self {
650 Self {
651 tree,
652 null_pages,
653 all_null_pages,
654 }
655 }
656
657 fn pages_eq(&self, query: &OrderableScalarValue) -> Vec<Matches> {
659 if query.0.is_null() {
660 self.pages_null()
661 } else {
662 self.pages_between((Bound::Included(query), Bound::Excluded(query)))
663 }
664 }
665
666 fn pages_in(&self, values: impl IntoIterator<Item = OrderableScalarValue>) -> Vec<Matches> {
668 let page_lists = values
671 .into_iter()
672 .map(|val| {
673 self.pages_eq(&val)
674 .into_iter()
675 .map(|matches| matches.page_id())
676 })
677 .collect::<Vec<_>>();
678 let total_size = page_lists.iter().map(|set| set.len()).sum();
679 let mut heap = BinaryHeap::with_capacity(total_size);
680 for page_list in page_lists {
681 heap.extend(page_list);
682 }
683 let mut all_pages = heap.into_sorted_vec();
684 all_pages.dedup();
685 all_pages.into_iter().map(Matches::Some).collect()
686 }
687
688 fn pages_between(
690 &self,
691 range: (Bound<&OrderableScalarValue>, Bound<&OrderableScalarValue>),
692 ) -> Vec<Matches> {
693 let lower_bound = match range.0 {
696 Bound::Unbounded => Bound::Unbounded,
697 Bound::Included(lower) => self
704 .tree
705 .largest_node_less(lower)
706 .map(|val| Bound::Included(val.0))
707 .unwrap_or(Bound::Unbounded),
708 Bound::Excluded(lower) => self
709 .tree
710 .largest_node_less(lower)
711 .map(|val| Bound::Included(val.0))
712 .unwrap_or(Bound::Unbounded),
713 };
714 let upper_bound = match range.1 {
715 Bound::Unbounded => Bound::Unbounded,
716 Bound::Included(upper) => Bound::Included(upper),
717 Bound::Excluded(upper) => Bound::Included(upper),
726 };
727
728 match (lower_bound, upper_bound) {
729 (Bound::Excluded(lower), Bound::Excluded(upper))
730 | (Bound::Excluded(lower), Bound::Included(upper))
731 | (Bound::Included(lower), Bound::Excluded(upper)) => {
732 if lower >= upper {
735 return vec![];
736 }
737 }
738 (Bound::Included(lower), Bound::Included(upper)) => {
739 if lower > upper {
740 return vec![];
741 }
742 }
743 _ => {}
744 }
745
746 let mut matches = Vec::new();
747
748 for (min, page_records) in self.tree.range((lower_bound, upper_bound)) {
749 for page_record in page_records {
750 match lower_bound {
751 Bound::Unbounded => {}
752 Bound::Included(lower) => {
753 if page_record.max.cmp(lower) == Ordering::Less {
754 continue;
755 }
756 }
757 Bound::Excluded(lower) => {
758 if page_record.max.cmp(lower) != Ordering::Greater {
759 continue;
760 }
761 }
762 }
763 if min.0.is_null() || page_record.max.0.is_null() {
767 matches.push(Matches::Some(page_record.page_number));
769 continue;
770 }
771
772 match range.0 {
773 Bound::Excluded(lower) => {
776 if min.cmp(lower) != Ordering::Greater {
777 matches.push(Matches::Some(page_record.page_number));
778 continue;
779 }
780 }
781 Bound::Included(lower) => {
784 if min.cmp(lower) == Ordering::Less {
785 matches.push(Matches::Some(page_record.page_number));
786 continue;
787 }
788 }
789 Bound::Unbounded => {}
790 }
791 match range.1 {
792 Bound::Excluded(upper) => {
795 if page_record.max.cmp(upper) != Ordering::Less {
796 matches.push(Matches::Some(page_record.page_number));
797 continue;
798 }
799 }
800 Bound::Included(upper) => {
803 if page_record.max.cmp(upper) == Ordering::Greater {
804 matches.push(Matches::Some(page_record.page_number));
805 continue;
806 }
807 }
808 Bound::Unbounded => {}
809 }
810 matches.push(Matches::All(page_record.page_number));
813 }
814 }
815
816 matches
817 }
818
819 fn pages_null(&self) -> Vec<Matches> {
820 self.null_pages
821 .iter()
822 .map(|page_id| Matches::Some(*page_id))
823 .chain(self.all_null_pages.iter().copied().map(Matches::All))
824 .collect()
825 }
826}
827
828#[derive(Clone)]
831struct LazyIndexReader {
832 index_reader: Arc<tokio::sync::Mutex<Option<Arc<dyn IndexReader>>>>,
833 store: Arc<dyn IndexStore>,
834 ranges_to_files: Option<Arc<RangeInclusiveMap<u32, (String, u32)>>>,
835}
836
837impl LazyIndexReader {
838 fn new(
839 store: Arc<dyn IndexStore>,
840 ranges_to_files: Option<Arc<RangeInclusiveMap<u32, (String, u32)>>>,
841 ) -> Self {
842 Self {
843 index_reader: Arc::new(tokio::sync::Mutex::new(None)),
844 store,
845 ranges_to_files,
846 }
847 }
848
849 async fn get(&self) -> Result<Arc<dyn IndexReader>> {
850 let mut reader = self.index_reader.lock().await;
851 if reader.is_none() {
852 let index_reader = if let Some(ranges_to_files) = &self.ranges_to_files {
853 Arc::new(LazyRangedIndexReader::new(
854 self.store.clone(),
855 ranges_to_files.clone(),
856 ))
857 } else {
858 self.store.open_index_file(BTREE_PAGES_NAME).await?
859 };
860 *reader = Some(index_reader);
861 }
862 Ok(reader.as_ref().unwrap().clone())
863 }
864}
865
866struct LazyRangedIndexReader {
868 #[allow(clippy::type_complexity)]
869 readers:
870 Arc<tokio::sync::Mutex<HashMap<String, Arc<tokio::sync::OnceCell<Arc<dyn IndexReader>>>>>>,
871 store: Arc<dyn IndexStore>,
872 ranges_to_files: Arc<RangeInclusiveMap<u32, (String, u32)>>,
873}
874
875impl LazyRangedIndexReader {
876 fn new(
877 store: Arc<dyn IndexStore>,
878 ranges_to_files: Arc<RangeInclusiveMap<u32, (String, u32)>>,
879 ) -> Self {
880 Self {
881 readers: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
882 store,
883 ranges_to_files,
884 }
885 }
886
887 async fn get_reader(&self, file_name: &str) -> Result<Arc<dyn IndexReader>> {
888 let reader_cell = {
889 let mut guard = self.readers.lock().await;
890 guard
891 .entry(file_name.to_string())
892 .or_insert_with(|| Arc::new(tokio::sync::OnceCell::new()))
893 .clone()
894 };
895 let reader = reader_cell
896 .get_or_try_init(|| async { self.store.open_index_file(file_name).await })
897 .await?;
898 Ok(reader.clone())
899 }
900
901 async fn get_reader_and_local_page_idx(
902 &self,
903 page_idx: u32,
904 ) -> Result<(Arc<dyn IndexReader>, u32)> {
905 let (page_file_name, offset) = self.ranges_to_files.get(&page_idx).ok_or_else(|| {
906 Error::internal(format!(
907 "Unexpected page index, index {} is out of range.",
908 page_idx
909 ))
910 })?;
911 let reader = self.get_reader(page_file_name).await?;
912 Ok((reader.clone(), page_idx - *offset))
913 }
914}
915
916#[async_trait]
917impl IndexReader for LazyRangedIndexReader {
918 async fn read_record_batch(&self, n: u64, batch_size: u64) -> Result<RecordBatch> {
919 let (reader, local_page_idx) = self.get_reader_and_local_page_idx(n as u32).await?;
920 reader
921 .read_record_batch(local_page_idx as u64, batch_size)
922 .await
923 }
924
925 async fn read_range(
926 &self,
927 _range: std::ops::Range<usize>,
928 _projection: Option<&[&str]>,
929 ) -> Result<RecordBatch> {
930 unimplemented!("Read range is not implemented for lazy page file reader.");
931 }
932
933 async fn num_batches(&self, batch_size: u64) -> u32 {
934 let mut total_batches = 0;
935 for (_, (file_name, _)) in self.ranges_to_files.iter() {
936 let reader = self
937 .get_reader(file_name)
938 .await
939 .unwrap_or_else(|_| panic!("Cannot open page file {}.", file_name));
940 total_batches += reader.as_ref().num_batches(batch_size).await;
941 }
942 total_batches
943 }
944
945 fn num_rows(&self) -> usize {
946 unimplemented!("only async functions are available for lazy page index reader.");
947 }
948
949 fn schema(&self) -> &lance_core::datatypes::Schema {
950 unimplemented!("only async functions are available for lazy page index reader.");
951 }
952}
953
954#[derive(Debug, Clone, DeepSizeOf)]
969pub struct CachedScalarIndex(Arc<dyn ScalarIndex>);
970
971impl CachedScalarIndex {
972 pub fn new(index: Arc<dyn ScalarIndex>) -> Self {
973 Self(index)
974 }
975
976 pub fn into_inner(self) -> Arc<dyn ScalarIndex> {
977 self.0
978 }
979}
980
981#[derive(Debug, Clone)]
982pub struct BTreePageKey {
983 pub page_number: u32,
984}
985
986impl CacheKey for BTreePageKey {
987 type ValueType = FlatIndex;
988
989 fn key(&self) -> std::borrow::Cow<'_, str> {
990 format!("page-{}", self.page_number).into()
991 }
992}
993
994#[derive(Clone, Debug)]
997pub struct BTreeIndex {
998 page_lookup: Arc<BTreeLookup>,
999 index_cache: WeakLanceCache,
1000 store: Arc<dyn IndexStore>,
1001 data_type: DataType,
1002 batch_size: u64,
1003
1004 ranges_to_files: Option<Arc<RangeInclusiveMap<u32, (String, u32)>>>,
1033 frag_reuse_index: Option<Arc<FragReuseIndex>>,
1034}
1035
1036impl DeepSizeOf for BTreeIndex {
1037 fn deep_size_of_children(&self, context: &mut deepsize::Context) -> usize {
1038 self.page_lookup.deep_size_of_children(context) + self.store.deep_size_of_children(context)
1041 }
1042}
1043
1044impl BTreeIndex {
1045 #[allow(clippy::too_many_arguments)]
1046 fn new(
1047 page_lookup: Arc<BTreeLookup>,
1048 store: Arc<dyn IndexStore>,
1049 data_type: DataType,
1050 index_cache: WeakLanceCache,
1051 batch_size: u64,
1052 ranges_to_files: Option<Arc<RangeInclusiveMap<u32, (String, u32)>>>,
1053 frag_reuse_index: Option<Arc<FragReuseIndex>>,
1054 ) -> Self {
1055 Self {
1056 page_lookup,
1057 store,
1058 data_type,
1059 index_cache,
1060 batch_size,
1061 ranges_to_files,
1062 frag_reuse_index,
1063 }
1064 }
1065
1066 async fn lookup_page(
1067 &self,
1068 page_number: u32,
1069 index_reader: LazyIndexReader,
1070 metrics: &dyn MetricsCollector,
1071 ) -> Result<Arc<FlatIndex>> {
1072 self.index_cache
1073 .get_or_insert_with_key(BTreePageKey { page_number }, move || async move {
1074 self.read_page(page_number, index_reader, metrics).await
1075 })
1076 .await
1077 }
1078
1079 #[instrument(level = "debug", skip_all)]
1080 async fn read_page(
1081 &self,
1082 page_number: u32,
1083 index_reader: LazyIndexReader,
1084 metrics: &dyn MetricsCollector,
1085 ) -> Result<FlatIndex> {
1086 metrics.record_part_load();
1087 info!(target: TRACE_IO_EVENTS, r#type=IO_TYPE_LOAD_SCALAR_PART, index_type="btree", part_id=page_number);
1088 let index_reader = index_reader.get().await?;
1089 let mut serialized_page = index_reader
1090 .read_record_batch(page_number as u64, self.batch_size)
1091 .await?;
1092 if let Some(frag_reuse_index_ref) = self.frag_reuse_index.as_ref() {
1093 serialized_page =
1094 frag_reuse_index_ref.remap_row_ids_record_batch(serialized_page, 1)?;
1095 }
1096 FlatIndex::try_new(serialized_page)
1097 }
1098
1099 async fn search_page(
1100 &self,
1101 query: &SargableQuery,
1102 matches: Matches,
1103 index_reader: LazyIndexReader,
1104 metrics: &dyn MetricsCollector,
1105 ) -> Result<NullableRowAddrSet> {
1106 let subindex = self
1107 .lookup_page(matches.page_id(), index_reader, metrics)
1108 .await?;
1109
1110 match matches {
1111 Matches::Some(_) => {
1112 subindex.search(query, metrics)
1117 }
1118 Matches::All(_) => Ok(match query {
1119 SargableQuery::IsNull() => subindex.all_ignore_nulls(),
1121 _ => subindex.all(),
1122 }),
1123 }
1124 }
1125
1126 #[instrument(level = "debug", skip_all)]
1127 fn try_from_serialized(
1128 data: RecordBatch,
1129 store: Arc<dyn IndexStore>,
1130 index_cache: &LanceCache,
1131 batch_size: u64,
1132 ranges_to_files: Option<Arc<RangeInclusiveMap<u32, (String, u32)>>>,
1133 frag_reuse_index: Option<Arc<FragReuseIndex>>,
1134 ) -> Result<Self> {
1135 let mut map = BTreeMap::<OrderableScalarValue, Vec<PageRecord>>::new();
1136 let mut null_pages = Vec::<u32>::new();
1138 let mut all_null_pages = Vec::<u32>::new();
1140
1141 if data.num_rows() == 0 {
1142 let data_type = data.column(0).data_type().clone();
1143 let page_lookup = Arc::new(BTreeLookup::empty());
1144 return Ok(Self::new(
1145 page_lookup,
1146 store,
1147 data_type,
1148 WeakLanceCache::from(index_cache),
1149 batch_size,
1150 ranges_to_files,
1151 frag_reuse_index,
1152 ));
1153 }
1154
1155 let mins = data.column(0);
1156 let maxs = data.column(1);
1157 let null_counts = data
1158 .column(2)
1159 .as_any()
1160 .downcast_ref::<UInt32Array>()
1161 .unwrap();
1162 let page_numbers = data
1163 .column(3)
1164 .as_any()
1165 .downcast_ref::<UInt32Array>()
1166 .unwrap();
1167
1168 for idx in 0..data.num_rows() {
1169 let min = OrderableScalarValue(ScalarValue::try_from_array(&mins, idx)?);
1170 let max = OrderableScalarValue(ScalarValue::try_from_array(&maxs, idx)?);
1171 let null_count = null_counts.values()[idx];
1172 let page_number = page_numbers.values()[idx];
1173
1174 if max.0.is_null() {
1176 all_null_pages.push(page_number);
1177 continue;
1179 } else {
1180 map.entry(min)
1181 .or_default()
1182 .push(PageRecord { max, page_number });
1183 }
1184
1185 if null_count > 0 {
1186 null_pages.push(page_number);
1187 }
1188 }
1189
1190 let last_max = ScalarValue::try_from_array(&maxs, data.num_rows() - 1)?;
1191 map.entry(OrderableScalarValue(last_max)).or_default();
1192
1193 let data_type = mins.data_type();
1194
1195 let page_lookup = Arc::new(BTreeLookup::new(map, null_pages, all_null_pages));
1196
1197 Ok(Self::new(
1198 page_lookup,
1199 store,
1200 data_type.clone(),
1201 WeakLanceCache::from(index_cache),
1202 batch_size,
1203 ranges_to_files,
1204 frag_reuse_index,
1205 ))
1206 }
1207
1208 async fn load(
1209 store: Arc<dyn IndexStore>,
1210 frag_reuse_index: Option<Arc<FragReuseIndex>>,
1211 index_cache: &LanceCache,
1212 ) -> Result<Arc<Self>> {
1213 let page_lookup_file = store.open_index_file(BTREE_LOOKUP_NAME).await?;
1214 let num_rows_in_lookup = page_lookup_file.num_rows();
1215 let serialized_lookup = page_lookup_file
1216 .read_range(0..num_rows_in_lookup, None)
1217 .await?;
1218 let file_schema = page_lookup_file.schema();
1219 let batch_size = file_schema
1220 .metadata
1221 .get(BATCH_SIZE_META_KEY)
1222 .map(|bs| bs.parse().unwrap_or(DEFAULT_BTREE_BATCH_SIZE))
1223 .unwrap_or(DEFAULT_BTREE_BATCH_SIZE);
1224
1225 let range_partitioned = file_schema
1226 .metadata
1227 .get(RANGE_PARTITIONED_META_KEY)
1228 .map(|bs| bs.parse().unwrap_or(DEFAULT_RANGE_PARTITIONED))
1229 .unwrap_or(DEFAULT_RANGE_PARTITIONED);
1230 let ranges_to_files = if range_partitioned {
1234 let part_sizes_str = file_schema
1235 .metadata
1236 .get(PAGE_NUM_PER_RANGE_PARTITION_META_KEY)
1237 .expect("Range-partitioned Btree lookup file must have page-number-per-range-file metadata!");
1238 let part_sizes_vec: Vec<(u64, u32)> = serde_json::from_str(part_sizes_str)?;
1239 let mut offset: u32 = 0;
1240
1241 let range_map = part_sizes_vec
1242 .into_iter()
1243 .map(|(id, size)| {
1244 let range = offset..=(offset + size - 1);
1245 let file_with_size = (part_page_data_file_path(id), offset);
1246 offset += size;
1247 (range, file_with_size)
1248 })
1249 .collect();
1250
1251 Some(Arc::new(range_map))
1252 } else {
1253 None
1254 };
1255
1256 Ok(Arc::new(Self::try_from_serialized(
1257 serialized_lookup,
1258 store,
1259 index_cache,
1260 batch_size,
1261 ranges_to_files,
1262 frag_reuse_index,
1263 )?))
1264 }
1265
1266 fn train_schema(&self) -> Schema {
1268 let value_field = Field::new(VALUE_COLUMN_NAME, self.data_type.clone(), true);
1269 let row_id_field = Field::new(ROW_ID, DataType::UInt64, false);
1270 Schema::new(vec![value_field, row_id_field])
1271 }
1272
1273 async fn into_data_stream(self) -> Result<SendableRecordBatchStream> {
1275 let lazy_reader = LazyIndexReader::new(self.store.clone(), self.ranges_to_files.clone());
1276 let reader = lazy_reader.get().await?;
1277 let new_schema = Arc::new(self.train_schema());
1278 let new_schema_clone = new_schema.clone();
1279 let reader_stream = IndexReaderStream::new(reader, self.batch_size).await;
1280 let batches = reader_stream
1281 .map(|fut| fut.map_err(DataFusionError::from))
1282 .buffered(self.store.io_parallelism())
1283 .map_ok(move |batch| {
1284 RecordBatch::try_new(
1285 new_schema.clone(),
1286 vec![batch.column(0).clone(), batch.column(1).clone()],
1287 )
1288 .unwrap()
1289 })
1290 .boxed();
1291 Ok(Box::pin(RecordBatchStreamAdapter::new(
1292 new_schema_clone,
1293 batches,
1294 )))
1295 }
1296
1297 async fn combine_old_new(
1298 self,
1299 new_data: SendableRecordBatchStream,
1300 chunk_size: u64,
1301 valid_old_fragments: Option<RoaringBitmap>,
1302 ) -> Result<SendableRecordBatchStream> {
1303 let value_column_index = new_data.schema().index_of(VALUE_COLUMN_NAME)?;
1304
1305 let new_input = Arc::new(OneShotExec::new(new_data));
1306 let old_stream = self.into_data_stream().await?;
1307 let old_stream = match valid_old_fragments {
1308 Some(valid_frags) => filter_row_ids_by_fragments(old_stream, valid_frags),
1309 None => old_stream,
1310 };
1311 let old_input = Arc::new(OneShotExec::new(old_stream));
1312 debug_assert_eq!(
1313 old_input.schema().flattened_fields().len(),
1314 new_input.schema().flattened_fields().len()
1315 );
1316
1317 let sort_expr = PhysicalSortExpr {
1318 expr: Arc::new(Column::new(VALUE_COLUMN_NAME, value_column_index)),
1319 options: SortOptions {
1320 descending: false,
1321 nulls_first: true,
1322 },
1323 };
1324 let all_data = UnionExec::try_new(vec![old_input, new_input])?;
1327 let ordered = Arc::new(SortPreservingMergeExec::new([sort_expr].into(), all_data));
1328
1329 let unchunked = execute_plan(
1330 ordered,
1331 LanceExecutionOptions {
1332 use_spilling: true,
1333 ..Default::default()
1334 },
1335 )?;
1336 Ok(chunk_concat_stream(unchunked, chunk_size as usize))
1337 }
1338}
1339
1340fn filter_row_ids_by_fragments(
1344 stream: SendableRecordBatchStream,
1345 valid_fragments: RoaringBitmap,
1346) -> SendableRecordBatchStream {
1347 let schema = stream.schema();
1348 let filtered = stream.map(move |batch_result| {
1349 let batch = batch_result?;
1350 let row_ids = batch[ROW_ID]
1351 .as_any()
1352 .downcast_ref::<arrow_array::UInt64Array>()
1353 .expect("expected UInt64Array for row_id column");
1354 let mask: arrow_array::BooleanArray = row_ids
1355 .iter()
1356 .map(|id| id.map(|id| valid_fragments.contains((id >> 32) as u32)))
1357 .collect();
1358 Ok(arrow_select::filter::filter_record_batch(&batch, &mask)?)
1359 });
1360 Box::pin(RecordBatchStreamAdapter::new(schema, filtered))
1361}
1362
1363fn wrap_bound(bound: &Bound<ScalarValue>) -> Bound<OrderableScalarValue> {
1364 match bound {
1365 Bound::Unbounded => Bound::Unbounded,
1366 Bound::Included(val) => Bound::Included(OrderableScalarValue(val.clone())),
1367 Bound::Excluded(val) => Bound::Excluded(OrderableScalarValue(val.clone())),
1368 }
1369}
1370
1371fn serialize_with_display<T: Display, S: Serializer>(
1372 value: &Option<T>,
1373 serializer: S,
1374) -> std::result::Result<S::Ok, S::Error> {
1375 if let Some(value) = value {
1376 serializer.collect_str(value)
1377 } else {
1378 serializer.collect_str("N/A")
1379 }
1380}
1381
1382#[derive(Serialize)]
1383struct BTreeStatistics {
1384 #[serde(serialize_with = "serialize_with_display")]
1385 min: Option<OrderableScalarValue>,
1386 #[serde(serialize_with = "serialize_with_display")]
1387 max: Option<OrderableScalarValue>,
1388 num_pages: u32,
1389}
1390
1391#[async_trait]
1392impl Index for BTreeIndex {
1393 fn as_any(&self) -> &dyn Any {
1394 self
1395 }
1396
1397 fn as_index(self: Arc<Self>) -> Arc<dyn Index> {
1398 self
1399 }
1400
1401 fn as_vector_index(self: Arc<Self>) -> Result<Arc<dyn crate::vector::VectorIndex>> {
1402 Err(Error::not_supported_source(
1403 "BTreeIndex is not vector index".into(),
1404 ))
1405 }
1406
1407 async fn prewarm(&self) -> Result<()> {
1408 let index_reader = LazyIndexReader::new(self.store.clone(), self.ranges_to_files.clone());
1409 let reader = index_reader.get().await?;
1410 let num_pages = reader.num_batches(self.batch_size).await;
1411 let mut pages = stream::iter(0..num_pages)
1412 .map(|page_idx| {
1413 let index_reader = index_reader.clone();
1414 async move {
1415 let page = self
1416 .read_page(page_idx, index_reader, &NoOpMetricsCollector)
1417 .await?;
1418 Result::Ok((page_idx, page))
1419 }
1420 })
1421 .buffer_unordered(get_num_compute_intensive_cpus());
1422
1423 while let Some((page_idx, page)) = pages.try_next().await? {
1424 let inserted = self
1425 .index_cache
1426 .insert_with_key(
1427 &BTreePageKey {
1428 page_number: page_idx,
1429 },
1430 Arc::new(page),
1431 )
1432 .await;
1433
1434 if !inserted {
1435 return Err(Error::internal(
1436 "Failed to prewarm index: cache is no longer available".to_string(),
1437 ));
1438 }
1439 }
1440
1441 Ok(())
1442 }
1443
1444 fn index_type(&self) -> IndexType {
1445 IndexType::BTree
1446 }
1447
1448 fn statistics(&self) -> Result<serde_json::Value> {
1449 let min = self
1450 .page_lookup
1451 .tree
1452 .first_key_value()
1453 .map(|(k, _)| k.clone());
1454 let max = self
1455 .page_lookup
1456 .tree
1457 .last_key_value()
1458 .map(|(k, _)| k.clone());
1459 serde_json::to_value(&BTreeStatistics {
1460 num_pages: self.page_lookup.tree.len() as u32,
1461 min,
1462 max,
1463 })
1464 .map_err(|err| err.into())
1465 }
1466
1467 async fn calculate_included_frags(&self) -> Result<RoaringBitmap> {
1468 let mut frag_ids = RoaringBitmap::default();
1469
1470 let lazy_reader = LazyIndexReader::new(self.store.clone(), self.ranges_to_files.clone());
1471 let sub_index_reader = lazy_reader.get().await?;
1472 let mut reader_stream = IndexReaderStream::new(sub_index_reader, self.batch_size)
1473 .await
1474 .buffered(self.store.io_parallelism());
1475 while let Some(serialized) = reader_stream.try_next().await? {
1476 let page = FlatIndex::try_new(serialized)?;
1477 frag_ids |= page.calculate_included_frags()?;
1478 }
1479
1480 Ok(frag_ids)
1481 }
1482}
1483
1484#[async_trait]
1485impl ScalarIndex for BTreeIndex {
1486 async fn search(
1487 &self,
1488 query: &dyn AnyQuery,
1489 metrics: &dyn MetricsCollector,
1490 ) -> Result<SearchResult> {
1491 let query = query.as_any().downcast_ref::<SargableQuery>().unwrap();
1492 let mut pages = match query {
1493 SargableQuery::Equals(val) => self
1494 .page_lookup
1495 .pages_eq(&OrderableScalarValue(val.clone())),
1496 SargableQuery::Range(start, end) => self
1497 .page_lookup
1498 .pages_between((wrap_bound(start).as_ref(), wrap_bound(end).as_ref())),
1499 SargableQuery::IsIn(values) => self
1500 .page_lookup
1501 .pages_in(values.iter().map(|val| OrderableScalarValue(val.clone()))),
1502 SargableQuery::FullTextSearch(_) => {
1503 return Err(Error::invalid_input(
1504 "full text search is not supported for BTree index, build a inverted index for it",
1505 ));
1506 }
1507 SargableQuery::IsNull() => self.page_lookup.pages_null(),
1508 };
1509
1510 if !matches!(query, SargableQuery::IsNull()) {
1520 let existing: HashSet<u32> = pages.iter().map(|m| m.page_id()).collect();
1521 for &page_id in self
1522 .page_lookup
1523 .null_pages
1524 .iter()
1525 .chain(self.page_lookup.all_null_pages.iter())
1526 {
1527 if !existing.contains(&page_id) {
1528 pages.push(Matches::Some(page_id));
1529 }
1530 }
1531 }
1532
1533 let lazy_index_reader =
1534 LazyIndexReader::new(self.store.clone(), self.ranges_to_files.clone());
1535 let page_tasks = pages
1536 .into_iter()
1537 .map(|page_index| {
1538 self.search_page(query, page_index, lazy_index_reader.clone(), metrics)
1539 .boxed()
1540 })
1541 .collect::<Vec<_>>();
1542 debug!("Searching {} btree pages", page_tasks.len());
1543
1544 let results: Vec<NullableRowAddrSet> = stream::iter(page_tasks)
1546 .buffered(get_num_compute_intensive_cpus())
1549 .try_collect()
1550 .await?;
1551
1552 let selection = NullableRowAddrSet::union_all(&results);
1554
1555 Ok(SearchResult::Exact(selection))
1556 }
1557
1558 fn can_remap(&self) -> bool {
1559 true
1560 }
1561
1562 async fn remap(
1563 &self,
1564 mapping: &HashMap<u64, Option<u64>>,
1565 dest_store: &dyn IndexStore,
1566 ) -> Result<CreatedIndex> {
1567 let part_page_files: Vec<(Option<u32>, &str)> =
1572 if let Some(ranges_to_files) = &self.ranges_to_files {
1573 ranges_to_files
1575 .iter()
1576 .enumerate()
1577 .map(|(part_id, (_, (path, _)))| (Some(part_id as u32), path.as_str()))
1578 .collect()
1579 } else {
1580 vec![(None, BTREE_PAGES_NAME)]
1582 };
1583
1584 let mapping = Arc::new(mapping.clone());
1585 let train_schema = Arc::new(self.train_schema());
1586
1587 for (part_id, page_file) in part_page_files {
1589 let sub_index_reader = self.store.open_index_file(page_file).await?;
1591 let mapping = mapping.clone();
1592
1593 let train_schema_clone = train_schema.clone();
1594 let train_schema = train_schema.clone();
1595
1596 let remapped_stream = IndexReaderStream::new(sub_index_reader, self.batch_size)
1597 .await
1598 .buffered(self.store.io_parallelism())
1599 .map_err(DataFusionError::from)
1600 .and_then(move |batch| {
1601 let remapped =
1603 FlatIndex::remap_batch(batch, &mapping).map_err(DataFusionError::from);
1604 let with_train_schema = remapped.and_then(|batch| {
1605 RecordBatch::try_new(train_schema.clone(), batch.columns().to_vec())
1606 .map_err(DataFusionError::from)
1607 });
1608 std::future::ready(with_train_schema)
1609 });
1610
1611 let remapped_stream = Box::pin(RecordBatchStreamAdapter::new(
1612 train_schema_clone,
1613 remapped_stream,
1614 ));
1615
1616 train_btree_index(remapped_stream, dest_store, self.batch_size, None, part_id).await?;
1617 }
1618
1619 if let Some(ranges_to_files) = &self.ranges_to_files {
1620 let num_parts = ranges_to_files.len();
1621 let page_files = (0..num_parts)
1623 .map(|part_id| part_page_data_file_path((part_id as u64) << 32))
1624 .collect::<Vec<_>>();
1625 let lookup_files = (0..num_parts)
1626 .map(|part_id| part_lookup_file_path((part_id as u64) << 32))
1627 .collect::<Vec<_>>();
1628 merge_metadata_files(dest_store, &page_files, &lookup_files, None).await?;
1629 }
1630
1631 Ok(CreatedIndex {
1632 index_details: prost_types::Any::from_msg(&pbold::BTreeIndexDetails::default())
1633 .unwrap(),
1634 index_version: BTREE_INDEX_VERSION,
1635 })
1636 }
1637
1638 async fn update(
1639 &self,
1640 new_data: SendableRecordBatchStream,
1641 dest_store: &dyn IndexStore,
1642 valid_old_fragments: Option<&RoaringBitmap>,
1643 ) -> Result<CreatedIndex> {
1644 let merged_data_source = self
1646 .clone()
1647 .combine_old_new(new_data, self.batch_size, valid_old_fragments.cloned())
1648 .await?;
1649 train_btree_index(merged_data_source, dest_store, self.batch_size, None, None).await?;
1650
1651 Ok(CreatedIndex {
1652 index_details: prost_types::Any::from_msg(&pbold::BTreeIndexDetails::default())
1653 .unwrap(),
1654 index_version: BTREE_INDEX_VERSION,
1655 })
1656 }
1657
1658 fn update_criteria(&self) -> UpdateCriteria {
1659 UpdateCriteria::only_new_data(TrainingCriteria::new(TrainingOrdering::Values).with_row_id())
1660 }
1661
1662 fn derive_index_params(&self) -> Result<ScalarIndexParams> {
1663 let params = serde_json::to_value(BTreeParameters {
1664 zone_size: Some(self.batch_size),
1665 range_id: None,
1666 })?;
1667 Ok(ScalarIndexParams::for_builtin(BuiltinIndexType::BTree).with_params(¶ms))
1668 }
1669}
1670
1671struct BatchStats {
1672 min: ScalarValue,
1673 max: ScalarValue,
1674 null_count: u32,
1675}
1676
1677fn analyze_batch(batch: &RecordBatch) -> Result<BatchStats> {
1678 let values = batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?;
1679 if values.is_empty() {
1680 return Err(Error::internal(
1681 "received an empty batch in btree training".to_string(),
1682 ));
1683 }
1684 let min = ScalarValue::try_from_array(&values, 0)
1685 .map_err(|e| Error::internal(format!("failed to get min value from batch: {}", e)))?;
1686 let max = ScalarValue::try_from_array(&values, values.len() - 1)
1687 .map_err(|e| Error::internal(format!("failed to get max value from batch: {}", e)))?;
1688
1689 Ok(BatchStats {
1690 min,
1691 max,
1692 null_count: values.null_count() as u32,
1693 })
1694}
1695
1696#[async_trait]
1698pub trait BTreeSubIndex: Debug + Send + Sync + DeepSizeOf {
1699 async fn train(&self, batch: RecordBatch) -> Result<RecordBatch>;
1701
1702 async fn load_subindex(&self, serialized: RecordBatch) -> Result<Arc<dyn ScalarIndex>>;
1704
1705 async fn retrieve_data(&self, serialized: RecordBatch) -> Result<RecordBatch>;
1712
1713 fn schema(&self) -> &Arc<Schema>;
1715
1716 async fn remap_subindex(
1718 &self,
1719 serialized: RecordBatch,
1720 mapping: &HashMap<u64, Option<u64>>,
1721 ) -> Result<RecordBatch>;
1722}
1723
1724struct EncodedBatch {
1725 stats: BatchStats,
1726 page_number: u32,
1727}
1728
1729async fn train_btree_page(
1730 batch: RecordBatch,
1731 batch_idx: u32,
1732 writer: &mut dyn IndexWriter,
1733 schema: Arc<Schema>,
1734) -> Result<EncodedBatch> {
1735 let stats = analyze_batch(&batch)?;
1736
1737 let trained = RecordBatch::try_new(
1739 schema.clone(),
1740 vec![
1741 batch.column_by_name(VALUE_COLUMN_NAME).expect_ok()?.clone(),
1742 batch.column_by_name(ROW_ID).expect_ok()?.clone(),
1743 ],
1744 )?;
1745
1746 writer.write_record_batch(trained).await?;
1747 Ok(EncodedBatch {
1748 stats,
1749 page_number: batch_idx,
1750 })
1751}
1752
1753fn btree_stats_as_batch(stats: Vec<EncodedBatch>, value_type: &DataType) -> Result<RecordBatch> {
1754 let mins = if stats.is_empty() {
1755 new_empty_array(value_type)
1756 } else {
1757 ScalarValue::iter_to_array(stats.iter().map(|stat| stat.stats.min.clone()))?
1758 };
1759 let maxs = if stats.is_empty() {
1760 new_empty_array(value_type)
1761 } else {
1762 ScalarValue::iter_to_array(stats.iter().map(|stat| stat.stats.max.clone()))?
1763 };
1764 let null_counts = UInt32Array::from_iter_values(stats.iter().map(|stat| stat.stats.null_count));
1765 let page_numbers = UInt32Array::from_iter_values(stats.iter().map(|stat| stat.page_number));
1766
1767 let schema = Arc::new(Schema::new(vec![
1768 Field::new("min", mins.data_type().clone(), true),
1770 Field::new("max", maxs.data_type().clone(), true),
1771 Field::new("null_count", null_counts.data_type().clone(), false),
1772 Field::new("page_idx", page_numbers.data_type().clone(), false),
1773 ]));
1774
1775 let columns = vec![
1776 mins,
1777 maxs,
1778 Arc::new(null_counts) as Arc<dyn Array>,
1779 Arc::new(page_numbers) as Arc<dyn Array>,
1780 ];
1781
1782 Ok(RecordBatch::try_new(schema, columns)?)
1783}
1784
1785pub async fn train_btree_index(
1787 batches_source: SendableRecordBatchStream,
1788 index_store: &dyn IndexStore,
1789 batch_size: u64,
1790 fragment_ids: Option<Vec<u32>>,
1791 range_id: Option<u32>,
1792) -> Result<()> {
1793 let partition_id = fragment_ids
1798 .as_ref()
1799 .and_then(|frag_ids| frag_ids.first())
1803 .map(|&first_frag_id| (first_frag_id as u64) << 32)
1804 .or_else(|| range_id.map(|id| (id as u64) << 32));
1808
1809 let flat_schema = Arc::new(Schema::new(vec![
1810 Field::new(
1811 BTREE_VALUES_COLUMN,
1812 batches_source.schema().field(0).data_type().clone(),
1813 true,
1814 ),
1815 Field::new(BTREE_IDS_COLUMN, DataType::UInt64, false),
1816 ]));
1817
1818 let mut sub_index_file = match partition_id {
1819 None => {
1820 index_store
1821 .new_index_file(BTREE_PAGES_NAME, flat_schema.clone())
1822 .await?
1823 }
1824 Some(partition_id) => {
1825 index_store
1826 .new_index_file(
1827 part_page_data_file_path(partition_id).as_str(),
1828 flat_schema.clone(),
1829 )
1830 .await?
1831 }
1832 };
1833
1834 let mut encoded_batches = Vec::new();
1835 let mut batch_idx = 0;
1836
1837 let value_type = batches_source
1838 .schema()
1839 .field_with_name(VALUE_COLUMN_NAME)?
1840 .data_type()
1841 .clone();
1842
1843 let mut batches_source = chunk_concat_stream(batches_source, batch_size as usize);
1844
1845 while let Some(batch) = batches_source.try_next().await? {
1846 encoded_batches.push(
1847 train_btree_page(
1848 batch,
1849 batch_idx,
1850 sub_index_file.as_mut(),
1851 flat_schema.clone(),
1852 )
1853 .await?,
1854 );
1855 batch_idx += 1;
1856 }
1857 sub_index_file.finish().await?;
1858 let record_batch = btree_stats_as_batch(encoded_batches, &value_type)?;
1859 let mut file_schema = record_batch.schema().as_ref().clone();
1860 file_schema
1861 .metadata
1862 .insert(BATCH_SIZE_META_KEY.to_string(), batch_size.to_string());
1863 file_schema.metadata.insert(
1864 RANGE_PARTITIONED_META_KEY.to_string(),
1865 range_id.is_some().to_string(),
1866 );
1867 let mut btree_index_file = match partition_id {
1868 None => {
1869 index_store
1870 .new_index_file(BTREE_LOOKUP_NAME, Arc::new(file_schema))
1871 .await?
1872 }
1873 Some(partition_id) => {
1874 index_store
1875 .new_index_file(
1876 part_lookup_file_path(partition_id).as_str(),
1877 Arc::new(file_schema),
1878 )
1879 .await?
1880 }
1881 };
1882 btree_index_file.write_record_batch(record_batch).await?;
1883 btree_index_file.finish().await?;
1884 Ok(())
1885}
1886
1887pub async fn merge_index_files(
1888 object_store: &ObjectStore,
1889 index_dir: &Path,
1890 store: Arc<dyn IndexStore>,
1891 batch_readhead: Option<usize>,
1892) -> Result<()> {
1893 let (part_page_files, part_lookup_files) =
1895 list_page_lookup_files(object_store, index_dir).await?;
1896 merge_metadata_files(
1897 store.as_ref(),
1898 &part_page_files,
1899 &part_lookup_files,
1900 batch_readhead,
1901 )
1902 .await
1903}
1904
1905async fn list_page_lookup_files(
1908 object_store: &ObjectStore,
1909 index_dir: &Path,
1910) -> Result<(Vec<String>, Vec<String>)> {
1911 let mut part_page_files = Vec::new();
1912 let mut part_lookup_files = Vec::new();
1913
1914 let mut list_stream = object_store.list(Some(index_dir.clone()));
1915
1916 while let Some(item) = list_stream.next().await {
1917 match item {
1918 Ok(meta) => {
1919 let file_name = meta.location.filename().unwrap_or_default();
1920 if file_name.starts_with("part_") && file_name.ends_with("_page_data.lance") {
1922 part_page_files.push(file_name.to_string());
1923 }
1924 if file_name.starts_with("part_") && file_name.ends_with("_page_lookup.lance") {
1926 part_lookup_files.push(file_name.to_string());
1927 }
1928 }
1929 Err(_) => continue,
1930 }
1931 }
1932
1933 if part_page_files.is_empty() || part_lookup_files.is_empty() {
1934 return Err(Error::internal(format!(
1935 "No partition metadata files found in index directory: {} (page_files: {}, lookup_files: {})",
1936 index_dir,
1937 part_page_files.len(),
1938 part_lookup_files.len()
1939 )));
1940 }
1941
1942 Ok((part_page_files, part_lookup_files))
1943}
1944
1945async fn merge_metadata_files(
1952 store: &dyn IndexStore,
1953 part_page_files: &[String],
1954 part_lookup_files: &[String],
1955 batch_readhead: Option<usize>,
1956) -> Result<()> {
1957 if part_lookup_files.is_empty() || part_page_files.is_empty() {
1958 return Err(Error::internal(
1959 "No partition files provided for merging".to_string(),
1960 ));
1961 }
1962
1963 if part_lookup_files.len() != part_page_files.len() {
1965 return Err(Error::internal(format!(
1966 "Number of partition lookup files ({}) does not match number of partition page files ({})",
1967 part_lookup_files.len(),
1968 part_page_files.len()
1969 )));
1970 }
1971 let mut page_files_map = HashMap::new();
1972 for page_file in part_page_files {
1973 let partition_id = extract_partition_id(page_file)?;
1974 page_files_map.insert(partition_id, page_file);
1975 }
1976
1977 for lookup_file in part_lookup_files {
1979 let partition_id = extract_partition_id(lookup_file)?;
1980 if !page_files_map.contains_key(&partition_id) {
1981 return Err(Error::internal(format!(
1982 "No corresponding page file found for lookup file: {} (partition_id: {})",
1983 lookup_file, partition_id
1984 )));
1985 }
1986 }
1987
1988 let first_lookup_reader = store.open_index_file(&part_lookup_files[0]).await?;
1990 let batch_size = first_lookup_reader
1991 .schema()
1992 .metadata
1993 .get(BATCH_SIZE_META_KEY)
1994 .map(|bs| bs.parse().unwrap_or(DEFAULT_BTREE_BATCH_SIZE))
1995 .unwrap_or(DEFAULT_BTREE_BATCH_SIZE);
1996 let range_partitioned = first_lookup_reader
1997 .schema()
1998 .metadata
1999 .get(RANGE_PARTITIONED_META_KEY)
2000 .map(|bs| bs.parse().unwrap_or(DEFAULT_RANGE_PARTITIONED))
2001 .unwrap_or(DEFAULT_RANGE_PARTITIONED);
2002
2003 let value_type = first_lookup_reader
2005 .schema()
2006 .fields
2007 .first()
2008 .unwrap()
2009 .data_type();
2010
2011 let mut metadata = HashMap::new();
2012 metadata.insert(BATCH_SIZE_META_KEY.to_string(), batch_size.to_string());
2013 let lookup_schema = Arc::new(Schema::new(vec![
2014 Field::new("min", value_type.clone(), true),
2015 Field::new("max", value_type.clone(), true),
2016 Field::new("null_count", DataType::UInt32, false),
2017 Field::new("page_idx", DataType::UInt32, false),
2018 ]));
2019
2020 if range_partitioned {
2022 merge_range_partitioned_lookups(
2023 store,
2024 part_lookup_files,
2025 lookup_schema,
2026 metadata,
2027 batch_size,
2028 batch_readhead,
2029 )
2030 .await
2031 } else {
2032 merge_pages_and_lookups(
2033 store,
2034 part_page_files,
2035 part_lookup_files,
2036 &page_files_map,
2037 lookup_schema,
2038 metadata,
2039 batch_size,
2040 batch_readhead,
2041 )
2042 .await
2043 }
2044}
2045
2046async fn merge_range_partitioned_lookups(
2076 store: &dyn IndexStore,
2077 part_lookup_files: &[String],
2078 lookup_schema: Arc<Schema>,
2079 mut metadata: HashMap<String, String>,
2080 batch_size: u64,
2081 batch_readhead: Option<usize>,
2082) -> Result<()> {
2083 let sorted_part_lookup_files = sort_files_by_partition_id(part_lookup_files)?;
2084 let mut lookup_file = store
2085 .new_index_file(BTREE_LOOKUP_NAME, lookup_schema)
2086 .await?;
2087
2088 let mut pages_per_file: Vec<(u64, u32)> = Vec::with_capacity(sorted_part_lookup_files.len());
2090 let mut num_pages_written = 0u32;
2091
2092 for (part_id, part_lookup_file) in sorted_part_lookup_files {
2093 let lookup_reader = store.open_index_file(&part_lookup_file).await?;
2094 let reader_stream = IndexReaderStream::new(lookup_reader.clone(), batch_size).await;
2095 let mut stream = reader_stream.buffered(batch_readhead.unwrap_or(1)).boxed();
2096 while let Some(batch) = stream.next().await {
2097 let original_batch = batch?;
2098 let modified_batch = add_offset_to_page_idx(&original_batch, num_pages_written)?;
2099 lookup_file.write_record_batch(modified_batch).await?;
2100 }
2101 pages_per_file.push((part_id, lookup_reader.num_rows() as u32));
2102 num_pages_written += lookup_reader.num_rows() as u32;
2103 }
2104
2105 metadata.insert(RANGE_PARTITIONED_META_KEY.to_string(), "true".to_string());
2106 metadata.insert(
2107 PAGE_NUM_PER_RANGE_PARTITION_META_KEY.to_string(),
2108 serde_json::to_string(&pages_per_file)?,
2109 );
2110
2111 lookup_file.finish_with_metadata(metadata).await?;
2112
2113 cleanup_partition_files(store, part_lookup_files, &[]).await;
2115 Ok(())
2116}
2117
2118#[allow(clippy::too_many_arguments)]
2124async fn merge_pages_and_lookups(
2125 store: &dyn IndexStore,
2126 part_page_files: &[String],
2127 part_lookup_files: &[String],
2128 page_files_map: &HashMap<u64, &String>,
2129 lookup_schema: Arc<Schema>,
2130 metadata: HashMap<String, String>,
2131 batch_size: u64,
2132 batch_readhead: Option<usize>,
2133) -> Result<()> {
2134 let partition_id = extract_partition_id(part_lookup_files[0].as_str())?;
2136 let page_file = page_files_map.get(&partition_id).unwrap();
2137 let page_reader = store.open_index_file(page_file).await?;
2138 let page_schema = page_reader.schema().clone();
2139
2140 let arrow_schema = Arc::new(Schema::from(&page_schema));
2141 let mut page_file = store
2142 .new_index_file(BTREE_PAGES_NAME, arrow_schema.clone())
2143 .await?;
2144
2145 let lookup_entries = merge_pages(
2146 part_lookup_files,
2147 page_files_map,
2148 store,
2149 batch_size,
2150 &mut page_file,
2151 arrow_schema.clone(),
2152 batch_readhead,
2153 )
2154 .await?;
2155 page_file.finish().await?;
2156
2157 let lookup_batch = RecordBatch::try_new(
2158 lookup_schema.clone(),
2159 vec![
2160 ScalarValue::iter_to_array(lookup_entries.iter().map(|(min, _, _, _)| min.clone()))?,
2161 ScalarValue::iter_to_array(lookup_entries.iter().map(|(_, max, _, _)| max.clone()))?,
2162 Arc::new(UInt32Array::from_iter_values(
2163 lookup_entries
2164 .iter()
2165 .map(|(_, _, null_count, _)| *null_count),
2166 )),
2167 Arc::new(UInt32Array::from_iter_values(
2168 lookup_entries.iter().map(|(_, _, _, page_idx)| *page_idx),
2169 )),
2170 ],
2171 )?;
2172 let mut lookup_file = store
2173 .new_index_file(BTREE_LOOKUP_NAME, lookup_schema)
2174 .await?;
2175 lookup_file.write_record_batch(lookup_batch).await?;
2176 lookup_file.finish_with_metadata(metadata).await?;
2177
2178 cleanup_partition_files(store, part_lookup_files, part_page_files).await;
2181
2182 Ok(())
2183}
2184
2185fn add_offset_to_page_idx(batch: &RecordBatch, offset: u32) -> Result<RecordBatch> {
2187 let (page_idx_pos, _) = batch.schema().column_with_name("page_idx").ok_or_else(|| {
2188 Error::internal("Column 'page_idx' not found in RecordBatch schema".to_string())
2189 })?;
2190 let page_idx_array = batch
2191 .column(page_idx_pos)
2192 .as_any()
2193 .downcast_ref::<UInt32Array>()
2194 .ok_or_else(|| {
2195 Error::internal("Failed to downcast 'page_idx' column to UInt32Array".to_string())
2196 })?;
2197 let offset_array = UInt32Array::from(vec![offset; page_idx_array.len()]);
2198 let new_page_idx_array_ref = add(page_idx_array, &offset_array)?;
2199 let mut new_columns = batch.columns().to_vec();
2200 new_columns[page_idx_pos] = new_page_idx_array_ref;
2201 let new_batch = RecordBatch::try_new(batch.schema(), new_columns)?;
2202 Ok(new_batch)
2203}
2204
2205async fn merge_pages(
2208 part_lookup_files: &[String],
2209 page_files_map: &HashMap<u64, &String>,
2210 store: &dyn IndexStore,
2211 batch_size: u64,
2212 page_file: &mut Box<dyn IndexWriter>,
2213 arrow_schema: Arc<Schema>,
2214 batch_readhead: Option<usize>,
2215) -> Result<Vec<(ScalarValue, ScalarValue, u32, u32)>> {
2216 let mut lookup_entries = Vec::new();
2217 let mut page_idx = 0u32;
2218
2219 debug!(
2220 "Starting SortPreservingMerge with {} partitions",
2221 part_lookup_files.len()
2222 );
2223
2224 let value_field = arrow_schema.field(0).clone().with_name(VALUE_COLUMN_NAME);
2225 let row_id_field = arrow_schema.field(1).clone().with_name(ROW_ID);
2226 let stream_schema = Arc::new(Schema::new(vec![value_field, row_id_field]));
2227
2228 let mut inputs: Vec<Arc<dyn ExecutionPlan>> = Vec::new();
2230 for lookup_file in part_lookup_files {
2231 let partition_id = extract_partition_id(lookup_file)?;
2232 let page_file_name = (*page_files_map.get(&partition_id).ok_or_else(|| {
2233 Error::internal(format!(
2234 "Page file not found for partition ID: {}",
2235 partition_id
2236 ))
2237 })?)
2238 .clone();
2239
2240 let reader = store.open_index_file(&page_file_name).await?;
2241
2242 let reader_stream = IndexReaderStream::new(reader, batch_size).await;
2243
2244 let stream = reader_stream
2245 .map(|fut| fut.map_err(DataFusionError::from))
2246 .buffered(batch_readhead.unwrap_or(1))
2247 .boxed();
2248
2249 let sendable_stream =
2250 Box::pin(RecordBatchStreamAdapter::new(stream_schema.clone(), stream));
2251 inputs.push(Arc::new(OneShotExec::new(sendable_stream)));
2252 }
2253
2254 let union_inputs = UnionExec::try_new(inputs)?;
2256
2257 let value_column_index = stream_schema.index_of(VALUE_COLUMN_NAME)?;
2259 let sort_expr = PhysicalSortExpr {
2260 expr: Arc::new(Column::new(VALUE_COLUMN_NAME, value_column_index)),
2261 options: SortOptions {
2262 descending: false,
2263 nulls_first: true,
2264 },
2265 };
2266
2267 let merge_exec = Arc::new(SortPreservingMergeExec::new(
2268 [sort_expr].into(),
2269 union_inputs,
2270 ));
2271
2272 let unchunked = execute_plan(
2273 merge_exec,
2274 LanceExecutionOptions {
2275 use_spilling: false,
2276 ..Default::default()
2277 },
2278 )?;
2279
2280 let mut chunked_stream = chunk_concat_stream(unchunked, batch_size as usize);
2282
2283 while let Some(batch) = chunked_stream.try_next().await? {
2285 let writer_batch = RecordBatch::try_new(
2286 arrow_schema.clone(),
2287 vec![batch.column(0).clone(), batch.column(1).clone()],
2288 )?;
2289
2290 page_file.write_record_batch(writer_batch).await?;
2291
2292 let min_val = ScalarValue::try_from_array(batch.column(0), 0)?;
2293 let max_val = ScalarValue::try_from_array(batch.column(0), batch.num_rows() - 1)?;
2294 let null_count = batch.column(0).null_count() as u32;
2295
2296 lookup_entries.push((min_val, max_val, null_count, page_idx));
2297 page_idx += 1;
2298 }
2299
2300 Ok(lookup_entries)
2301}
2302
2303fn sort_files_by_partition_id(part_files: &[String]) -> Result<Vec<(u64, String)>> {
2305 let mut files_with_ids: Vec<(u64, &String)> = part_files
2306 .iter()
2307 .map(|file| extract_partition_id(file).map(|id| (id, file)))
2308 .collect::<Result<Vec<_>>>()?;
2309
2310 files_with_ids.sort_unstable_by_key(|k| k.0);
2311
2312 let sorted_files = files_with_ids
2313 .into_iter()
2314 .map(|(id, file)| (id, file.clone()))
2315 .collect();
2316
2317 Ok(sorted_files)
2318}
2319
2320fn extract_partition_id(filename: &str) -> Result<u64> {
2323 if !filename.starts_with("part_") {
2324 return Err(Error::internal(format!(
2325 "Invalid partition file name format: {}",
2326 filename
2327 )));
2328 }
2329
2330 let parts: Vec<&str> = filename.split('_').collect();
2331 if parts.len() < 3 {
2332 return Err(Error::internal(format!(
2333 "Invalid partition file name format: {}",
2334 filename
2335 )));
2336 }
2337
2338 parts[1].parse::<u64>().map_err(|_| {
2339 Error::internal(format!(
2340 "Failed to parse partition ID from filename: {}",
2341 filename
2342 ))
2343 })
2344}
2345
2346async fn cleanup_partition_files(
2351 store: &dyn IndexStore,
2352 part_lookup_files: &[String],
2353 part_page_files: &[String],
2354) {
2355 for file_name in part_lookup_files {
2357 cleanup_single_file(
2358 store,
2359 file_name,
2360 "part_",
2361 "_page_lookup.lance",
2362 "partition lookup",
2363 )
2364 .await;
2365 }
2366
2367 for file_name in part_page_files {
2369 cleanup_single_file(
2370 store,
2371 file_name,
2372 "part_",
2373 "_page_data.lance",
2374 "partition page",
2375 )
2376 .await;
2377 }
2378}
2379
2380async fn cleanup_single_file(
2384 store: &dyn IndexStore,
2385 file_name: &str,
2386 expected_prefix: &str,
2387 expected_suffix: &str,
2388 file_type: &str,
2389) {
2390 if file_name.starts_with(expected_prefix) && file_name.ends_with(expected_suffix) {
2391 match store.delete_index_file(file_name).await {
2392 Ok(()) => {
2393 debug!("Successfully deleted {} file: {}", file_type, file_name);
2394 }
2395 Err(e) => {
2396 warn!(
2397 "Failed to delete {} file '{}': {}. \
2398 This does not affect the merge operation, but may leave \
2399 partition files that should be cleaned up manually.",
2400 file_type, file_name, e
2401 );
2402 }
2403 }
2404 } else {
2405 warn!(
2407 "Skipping deletion of file '{}' as it does not match the expected \
2408 {} file pattern ({}*{})",
2409 file_name, file_type, expected_prefix, expected_suffix
2410 );
2411 }
2412}
2413
2414pub(crate) fn part_page_data_file_path(partition_id: u64) -> String {
2415 format!("part_{}_{}", partition_id, BTREE_PAGES_NAME)
2416}
2417
2418pub(crate) fn part_lookup_file_path(partition_id: u64) -> String {
2419 format!("part_{}_{}", partition_id, BTREE_LOOKUP_NAME)
2420}
2421
2422struct IndexReaderStream {
2426 reader: Arc<dyn IndexReader>,
2427 batch_size: u64,
2428 num_batches: u32,
2429 batch_idx: u32,
2430}
2431
2432impl IndexReaderStream {
2433 async fn new(reader: Arc<dyn IndexReader>, batch_size: u64) -> Self {
2434 let num_batches = reader.num_batches(batch_size).await;
2435 Self {
2436 reader,
2437 batch_size,
2438 num_batches,
2439 batch_idx: 0,
2440 }
2441 }
2442}
2443
2444impl Stream for IndexReaderStream {
2445 type Item = BoxFuture<'static, Result<RecordBatch>>;
2446
2447 fn poll_next(
2448 self: std::pin::Pin<&mut Self>,
2449 _cx: &mut std::task::Context<'_>,
2450 ) -> std::task::Poll<Option<Self::Item>> {
2451 let this = self.get_mut();
2452 if this.batch_idx >= this.num_batches {
2453 return std::task::Poll::Ready(None);
2454 }
2455 let batch_num = this.batch_idx;
2456 this.batch_idx += 1;
2457 let reader_copy = this.reader.clone();
2458 let batch_size = this.batch_size;
2459 let read_task = async move {
2460 reader_copy
2461 .read_record_batch(batch_num as u64, batch_size)
2462 .await
2463 }
2464 .boxed();
2465 std::task::Poll::Ready(Some(read_task))
2466 }
2467}
2468
2469#[derive(Debug, Serialize, Deserialize)]
2471pub struct BTreeParameters {
2472 pub zone_size: Option<u64>,
2474
2475 pub range_id: Option<u32>,
2499}
2500
2501struct BTreeTrainingRequest {
2502 parameters: BTreeParameters,
2503 criteria: TrainingCriteria,
2504}
2505
2506impl BTreeTrainingRequest {
2507 pub fn new(parameters: BTreeParameters) -> Self {
2508 Self {
2509 parameters,
2510 criteria: TrainingCriteria::new(TrainingOrdering::Values).with_row_id(),
2512 }
2513 }
2514}
2515
2516impl TrainingRequest for BTreeTrainingRequest {
2517 fn as_any(&self) -> &dyn std::any::Any {
2518 self
2519 }
2520
2521 fn criteria(&self) -> &TrainingCriteria {
2522 &self.criteria
2523 }
2524}
2525
2526#[derive(Debug, Default)]
2527pub struct BTreeIndexPlugin;
2528
2529#[async_trait]
2530impl ScalarIndexPlugin for BTreeIndexPlugin {
2531 fn name(&self) -> &str {
2532 "BTree"
2533 }
2534
2535 fn new_training_request(
2536 &self,
2537 params: &str,
2538 field: &Field,
2539 ) -> Result<Box<dyn TrainingRequest>> {
2540 if field.data_type().is_nested() {
2541 return Err(Error::invalid_input_source(
2542 "A btree index can only be created on a non-nested field.".into(),
2543 ));
2544 }
2545
2546 let params = serde_json::from_str::<BTreeParameters>(params)?;
2547 Ok(Box::new(BTreeTrainingRequest::new(params)))
2548 }
2549
2550 fn provides_exact_answer(&self) -> bool {
2551 true
2552 }
2553
2554 fn version(&self) -> u32 {
2555 BTREE_INDEX_VERSION
2556 }
2557
2558 fn new_query_parser(
2559 &self,
2560 index_name: String,
2561 _index_details: &prost_types::Any,
2562 ) -> Option<Box<dyn ScalarQueryParser>> {
2563 Some(Box::new(SargableQueryParser::new(index_name, false)))
2564 }
2565
2566 async fn train_index(
2567 &self,
2568 data: SendableRecordBatchStream,
2569 index_store: &dyn IndexStore,
2570 request: Box<dyn TrainingRequest>,
2571 fragment_ids: Option<Vec<u32>>,
2572 _progress: Arc<dyn crate::progress::IndexBuildProgress>,
2573 ) -> Result<CreatedIndex> {
2574 let request = request
2575 .as_any()
2576 .downcast_ref::<BTreeTrainingRequest>()
2577 .unwrap();
2578 train_btree_index(
2579 data,
2580 index_store,
2581 request
2582 .parameters
2583 .zone_size
2584 .unwrap_or(DEFAULT_BTREE_BATCH_SIZE),
2585 fragment_ids,
2586 request.parameters.range_id,
2587 )
2588 .await?;
2589 Ok(CreatedIndex {
2590 index_details: prost_types::Any::from_msg(&pbold::BTreeIndexDetails::default())
2591 .unwrap(),
2592 index_version: BTREE_INDEX_VERSION,
2593 })
2594 }
2595
2596 async fn load_index(
2597 &self,
2598 index_store: Arc<dyn IndexStore>,
2599 _index_details: &prost_types::Any,
2600 frag_reuse_index: Option<Arc<FragReuseIndex>>,
2601 cache: &LanceCache,
2602 ) -> Result<Arc<dyn ScalarIndex>> {
2603 Ok(BTreeIndex::load(index_store, frag_reuse_index, cache).await? as Arc<dyn ScalarIndex>)
2604 }
2605}
2606
2607#[cfg(test)]
2608mod tests {
2609 use std::sync::atomic::Ordering;
2610 use std::{collections::HashMap, sync::Arc};
2611
2612 use arrow::datatypes::{Float32Type, Float64Type, Int32Type, UInt64Type};
2613 use arrow_array::{FixedSizeListArray, record_batch};
2614 use datafusion::{
2615 execution::{SendableRecordBatchStream, TaskContext},
2616 physical_plan::{ExecutionPlan, sorts::sort::SortExec, stream::RecordBatchStreamAdapter},
2617 };
2618 use datafusion_common::{DataFusionError, ScalarValue};
2619 use datafusion_physical_expr::{PhysicalSortExpr, expressions::col};
2620 use deepsize::DeepSizeOf;
2621 use futures::TryStreamExt;
2622 use futures::stream;
2623 use lance_core::utils::mask::RowSetOps;
2624 use lance_core::utils::tempfile::TempObjDir;
2625 use lance_core::{cache::LanceCache, utils::mask::RowAddrTreeMap};
2626 use lance_datafusion::{chunker::break_stream, datagen::DatafusionDatagenExt};
2627 use lance_datagen::{ArrayGeneratorExt, BatchCount, RowCount, array, gen_batch};
2628 use lance_io::object_store::ObjectStore;
2629 use object_store::path::Path;
2630
2631 use crate::metrics::LocalMetricsCollector;
2632 use crate::{
2633 metrics::NoOpMetricsCollector,
2634 scalar::{
2635 IndexStore, SargableQuery, ScalarIndex, SearchResult,
2636 btree::{BTREE_PAGES_NAME, BTreeIndex},
2637 lance_format::LanceIndexStore,
2638 },
2639 };
2640
2641 use super::{
2642 DEFAULT_BTREE_BATCH_SIZE, OrderableScalarValue, part_lookup_file_path,
2643 part_page_data_file_path, train_btree_index,
2644 };
2645 #[test]
2646 fn test_scalar_value_size() {
2647 let size_of_i32 = OrderableScalarValue(ScalarValue::Int32(Some(0))).deep_size_of();
2648 let size_of_many_i32 = OrderableScalarValue(ScalarValue::FixedSizeList(Arc::new(
2649 FixedSizeListArray::from_iter_primitive::<Int32Type, _, _>(
2650 vec![Some(vec![Some(0); 128])],
2651 128,
2652 ),
2653 )))
2654 .deep_size_of();
2655
2656 assert!(size_of_i32 > 4);
2658 assert!(size_of_many_i32 > 128 * 4);
2659 }
2660
2661 #[tokio::test]
2662 async fn test_null_ids() {
2663 let tmpdir = TempObjDir::default();
2664 let test_store = Arc::new(LanceIndexStore::new(
2665 Arc::new(ObjectStore::local()),
2666 tmpdir.clone(),
2667 Arc::new(LanceCache::no_cache()),
2668 ));
2669
2670 let stream = gen_batch()
2672 .col(
2673 "value",
2674 array::rand::<Float32Type>().with_nulls(&[true, false, false, false, false]),
2675 )
2676 .col("_rowid", array::step::<UInt64Type>())
2677 .into_df_stream(RowCount::from(5000), BatchCount::from(10));
2678
2679 train_btree_index(stream, test_store.as_ref(), 5000, None, None)
2680 .await
2681 .unwrap();
2682
2683 let index = BTreeIndex::load(test_store.clone(), None, &LanceCache::no_cache())
2684 .await
2685 .unwrap();
2686
2687 assert_eq!(index.page_lookup.null_pages.len(), 10);
2688
2689 let remap_dir = TempObjDir::default();
2690 let remap_store = Arc::new(LanceIndexStore::new(
2691 Arc::new(ObjectStore::local()),
2692 remap_dir.clone(),
2693 Arc::new(LanceCache::no_cache()),
2694 ));
2695
2696 index
2698 .remap(&HashMap::default(), remap_store.as_ref())
2699 .await
2700 .unwrap();
2701
2702 let remap_index = BTreeIndex::load(remap_store.clone(), None, &LanceCache::no_cache())
2703 .await
2704 .unwrap();
2705
2706 assert_eq!(remap_index.page_lookup, index.page_lookup);
2707
2708 let original_pages = test_store.open_index_file(BTREE_PAGES_NAME).await.unwrap();
2709 let remapped_pages = remap_store.open_index_file(BTREE_PAGES_NAME).await.unwrap();
2710
2711 assert_eq!(original_pages.num_rows(), remapped_pages.num_rows());
2712
2713 let original_data = original_pages
2714 .read_record_batch(0, original_pages.num_rows() as u64)
2715 .await
2716 .unwrap();
2717 let remapped_data = remapped_pages
2718 .read_record_batch(0, remapped_pages.num_rows() as u64)
2719 .await
2720 .unwrap();
2721
2722 assert_eq!(original_data, remapped_data);
2723 }
2724
2725 #[tokio::test]
2726 async fn test_nan_ordering() {
2727 let tmpdir = TempObjDir::default();
2728 let test_store = Arc::new(LanceIndexStore::new(
2729 Arc::new(ObjectStore::local()),
2730 tmpdir.clone(),
2731 Arc::new(LanceCache::no_cache()),
2732 ));
2733
2734 let values = vec![
2735 0.0,
2736 1.0,
2737 2.0,
2738 3.0,
2739 f64::NAN,
2740 f64::NEG_INFINITY,
2741 f64::INFINITY,
2742 ];
2743
2744 let data = gen_batch()
2748 .col("value", array::cycle::<Float64Type>(values.clone()))
2749 .col("_rowid", array::step::<UInt64Type>())
2750 .into_df_exec(RowCount::from(10), BatchCount::from(100));
2751 let schema = data.schema();
2752 let sort_expr = PhysicalSortExpr::new_default(col("value", schema.as_ref()).unwrap());
2753 let plan = Arc::new(SortExec::new([sort_expr].into(), data));
2754 let stream = plan.execute(0, Arc::new(TaskContext::default())).unwrap();
2755 let stream = break_stream(stream, 64);
2756 let stream = stream.map_err(DataFusionError::from);
2757 let stream =
2758 Box::pin(RecordBatchStreamAdapter::new(schema, stream)) as SendableRecordBatchStream;
2759
2760 train_btree_index(stream, test_store.as_ref(), 64, None, None)
2761 .await
2762 .unwrap();
2763
2764 let index = BTreeIndex::load(test_store, None, &LanceCache::no_cache())
2765 .await
2766 .unwrap();
2767
2768 for (idx, value) in values.into_iter().enumerate() {
2769 let query = SargableQuery::Equals(ScalarValue::Float64(Some(value)));
2770 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
2771 assert_eq!(
2772 result,
2773 SearchResult::exact(RowAddrTreeMap::from_iter(((idx as u64)..1000).step_by(7)))
2774 );
2775 }
2776 }
2777
2778 #[tokio::test]
2779 async fn test_page_cache() {
2780 let tmpdir = TempObjDir::default();
2781 let test_store = Arc::new(LanceIndexStore::new(
2782 Arc::new(ObjectStore::local()),
2783 tmpdir.clone(),
2784 Arc::new(LanceCache::no_cache()),
2785 ));
2786
2787 let data = gen_batch()
2788 .col("value", array::step::<Float32Type>())
2789 .col("_rowid", array::step::<UInt64Type>())
2790 .into_df_exec(RowCount::from(1000), BatchCount::from(10));
2791 let schema = data.schema();
2792 let sort_expr = PhysicalSortExpr::new_default(col("value", schema.as_ref()).unwrap());
2793 let plan = Arc::new(SortExec::new([sort_expr].into(), data));
2794 let stream = plan.execute(0, Arc::new(TaskContext::default())).unwrap();
2795 let stream = break_stream(stream, 64);
2796 let stream = stream.map_err(DataFusionError::from);
2797 let stream =
2798 Box::pin(RecordBatchStreamAdapter::new(schema, stream)) as SendableRecordBatchStream;
2799
2800 train_btree_index(stream, test_store.as_ref(), 64, None, None)
2801 .await
2802 .unwrap();
2803
2804 let cache = Arc::new(LanceCache::with_capacity(100 * 1024 * 1024));
2805 let index = BTreeIndex::load(test_store, None, cache.as_ref())
2806 .await
2807 .unwrap();
2808
2809 let query = SargableQuery::Equals(ScalarValue::Float32(Some(0.0)));
2810 let metrics = LocalMetricsCollector::default();
2811 let query1 = index.search(&query, &metrics);
2812 let query2 = index.search(&query, &metrics);
2813 tokio::join!(query1, query2).0.unwrap();
2814 assert_eq!(metrics.parts_loaded.load(Ordering::Relaxed), 1);
2815 }
2816
2817 #[tokio::test]
2818 async fn test_fragment_btree_index_consistency() {
2819 let full_tmpdir = TempObjDir::default();
2821 let full_store = Arc::new(LanceIndexStore::new(
2822 Arc::new(ObjectStore::local()),
2823 full_tmpdir.clone(),
2824 Arc::new(LanceCache::no_cache()),
2825 ));
2826
2827 let fragment_tmpdir = TempObjDir::default();
2828 let fragment_store = Arc::new(LanceIndexStore::new(
2829 Arc::new(ObjectStore::local()),
2830 fragment_tmpdir.clone(),
2831 Arc::new(LanceCache::no_cache()),
2832 ));
2833
2834 let total_count = 2 * DEFAULT_BTREE_BATCH_SIZE;
2837 let full_data_gen = gen_batch()
2838 .col("value", array::step::<Int32Type>())
2839 .col("_rowid", array::step::<UInt64Type>())
2840 .into_df_stream(RowCount::from(total_count / 2), BatchCount::from(2));
2841 let full_data_source = Box::pin(RecordBatchStreamAdapter::new(
2842 full_data_gen.schema(),
2843 full_data_gen,
2844 ));
2845
2846 train_btree_index(
2847 full_data_source,
2848 full_store.as_ref(),
2849 DEFAULT_BTREE_BATCH_SIZE,
2850 None,
2851 None,
2852 )
2853 .await
2854 .unwrap();
2855
2856 let half_count = DEFAULT_BTREE_BATCH_SIZE;
2859 let fragment1_gen = gen_batch()
2860 .col("value", array::step::<Int32Type>())
2861 .col("_rowid", array::step::<UInt64Type>())
2862 .into_df_stream(RowCount::from(half_count), BatchCount::from(1));
2863 let fragment1_data_source = Box::pin(RecordBatchStreamAdapter::new(
2864 fragment1_gen.schema(),
2865 fragment1_gen,
2866 ));
2867
2868 train_btree_index(
2869 fragment1_data_source,
2870 fragment_store.as_ref(),
2871 DEFAULT_BTREE_BATCH_SIZE,
2872 Some(vec![1]), None,
2874 )
2875 .await
2876 .unwrap();
2877
2878 let start_val = DEFAULT_BTREE_BATCH_SIZE as i32;
2880 let end_val = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
2881 let values_second_half: Vec<i32> = (start_val..end_val).collect();
2882 let row_ids_second_half: Vec<u64> = (start_val as u64..end_val as u64).collect();
2883 let fragment2_gen = gen_batch()
2884 .col("value", array::cycle::<Int32Type>(values_second_half))
2885 .col("_rowid", array::cycle::<UInt64Type>(row_ids_second_half))
2886 .into_df_stream(RowCount::from(half_count), BatchCount::from(1));
2887 let fragment2_data_source = Box::pin(RecordBatchStreamAdapter::new(
2888 fragment2_gen.schema(),
2889 fragment2_gen,
2890 ));
2891
2892 train_btree_index(
2893 fragment2_data_source,
2894 fragment_store.as_ref(),
2895 DEFAULT_BTREE_BATCH_SIZE,
2896 Some(vec![2]), None,
2898 )
2899 .await
2900 .unwrap();
2901
2902 let part_page_files = vec![
2904 part_page_data_file_path(1 << 32),
2905 part_page_data_file_path(2 << 32),
2906 ];
2907
2908 let part_lookup_files = vec![
2909 part_lookup_file_path(1 << 32),
2910 part_lookup_file_path(2 << 32),
2911 ];
2912
2913 super::merge_metadata_files(
2914 fragment_store.as_ref(),
2915 &part_page_files,
2916 &part_lookup_files,
2917 Option::from(1usize),
2918 )
2919 .await
2920 .unwrap();
2921
2922 let full_index = BTreeIndex::load(full_store.clone(), None, &LanceCache::no_cache())
2924 .await
2925 .unwrap();
2926
2927 let merged_index = BTreeIndex::load(fragment_store.clone(), None, &LanceCache::no_cache())
2928 .await
2929 .unwrap();
2930
2931 let query_0 = SargableQuery::Equals(ScalarValue::Int32(Some(0)));
2935 let full_result_0 = full_index
2936 .search(&query_0, &NoOpMetricsCollector)
2937 .await
2938 .unwrap();
2939 let merged_result_0 = merged_index
2940 .search(&query_0, &NoOpMetricsCollector)
2941 .await
2942 .unwrap();
2943 assert_eq!(full_result_0, merged_result_0, "Query for value 0 failed");
2944
2945 let mid_first_batch = (DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
2947 let query_mid_first = SargableQuery::Equals(ScalarValue::Int32(Some(mid_first_batch)));
2948 let full_result_mid_first = full_index
2949 .search(&query_mid_first, &NoOpMetricsCollector)
2950 .await
2951 .unwrap();
2952 let merged_result_mid_first = merged_index
2953 .search(&query_mid_first, &NoOpMetricsCollector)
2954 .await
2955 .unwrap();
2956 assert_eq!(
2957 full_result_mid_first, merged_result_mid_first,
2958 "Query for value {} failed",
2959 mid_first_batch
2960 );
2961
2962 let first_second_batch = DEFAULT_BTREE_BATCH_SIZE as i32;
2964 let query_first_second =
2965 SargableQuery::Equals(ScalarValue::Int32(Some(first_second_batch)));
2966 let full_result_first_second = full_index
2967 .search(&query_first_second, &NoOpMetricsCollector)
2968 .await
2969 .unwrap();
2970 let merged_result_first_second = merged_index
2971 .search(&query_first_second, &NoOpMetricsCollector)
2972 .await
2973 .unwrap();
2974 assert_eq!(
2975 full_result_first_second, merged_result_first_second,
2976 "Query for value {} failed",
2977 first_second_batch
2978 );
2979
2980 let mid_second_batch = (DEFAULT_BTREE_BATCH_SIZE + DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
2982 let query_mid_second = SargableQuery::Equals(ScalarValue::Int32(Some(mid_second_batch)));
2983
2984 let full_result_mid_second = full_index
2985 .search(&query_mid_second, &NoOpMetricsCollector)
2986 .await
2987 .unwrap();
2988 let merged_result_mid_second = merged_index
2989 .search(&query_mid_second, &NoOpMetricsCollector)
2990 .await
2991 .unwrap();
2992 assert_eq!(
2993 full_result_mid_second, merged_result_mid_second,
2994 "Query for value {} failed",
2995 mid_second_batch
2996 );
2997 }
2998
2999 #[tokio::test]
3000 async fn test_fragment_btree_index_boundary_queries() {
3001 let full_tmpdir = TempObjDir::default();
3003 let full_store = Arc::new(LanceIndexStore::new(
3004 Arc::new(ObjectStore::local()),
3005 full_tmpdir.clone(),
3006 Arc::new(LanceCache::no_cache()),
3007 ));
3008
3009 let fragment_tmpdir = TempObjDir::default();
3010 let fragment_store = Arc::new(LanceIndexStore::new(
3011 Arc::new(ObjectStore::local()),
3012 fragment_tmpdir.clone(),
3013 Arc::new(LanceCache::no_cache()),
3014 ));
3015
3016 let total_count = 3 * DEFAULT_BTREE_BATCH_SIZE;
3018
3019 let full_data_gen = gen_batch()
3021 .col("value", array::step::<Int32Type>())
3022 .col("_rowid", array::step::<UInt64Type>())
3023 .into_df_stream(RowCount::from(total_count / 3), BatchCount::from(3));
3024 let full_data_source = Box::pin(RecordBatchStreamAdapter::new(
3025 full_data_gen.schema(),
3026 full_data_gen,
3027 ));
3028
3029 train_btree_index(
3030 full_data_source,
3031 full_store.as_ref(),
3032 DEFAULT_BTREE_BATCH_SIZE,
3033 None,
3034 None,
3035 )
3036 .await
3037 .unwrap();
3038
3039 let fragment_size = DEFAULT_BTREE_BATCH_SIZE;
3042 let fragment1_gen = gen_batch()
3043 .col("value", array::step::<Int32Type>())
3044 .col("_rowid", array::step::<UInt64Type>())
3045 .into_df_stream(RowCount::from(fragment_size), BatchCount::from(1));
3046 let fragment1_data_source = Box::pin(RecordBatchStreamAdapter::new(
3047 fragment1_gen.schema(),
3048 fragment1_gen,
3049 ));
3050
3051 train_btree_index(
3052 fragment1_data_source,
3053 fragment_store.as_ref(),
3054 DEFAULT_BTREE_BATCH_SIZE,
3055 Some(vec![1]),
3056 None,
3057 )
3058 .await
3059 .unwrap();
3060
3061 let start_val2 = DEFAULT_BTREE_BATCH_SIZE as i32;
3063 let end_val2 = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3064 let values_fragment2: Vec<i32> = (start_val2..end_val2).collect();
3065 let row_ids_fragment2: Vec<u64> = (start_val2 as u64..end_val2 as u64).collect();
3066 let fragment2_gen = gen_batch()
3067 .col("value", array::cycle::<Int32Type>(values_fragment2))
3068 .col("_rowid", array::cycle::<UInt64Type>(row_ids_fragment2))
3069 .into_df_stream(RowCount::from(fragment_size), BatchCount::from(1));
3070 let fragment2_data_source = Box::pin(RecordBatchStreamAdapter::new(
3071 fragment2_gen.schema(),
3072 fragment2_gen,
3073 ));
3074
3075 train_btree_index(
3076 fragment2_data_source,
3077 fragment_store.as_ref(),
3078 DEFAULT_BTREE_BATCH_SIZE,
3079 Some(vec![2]),
3080 None,
3081 )
3082 .await
3083 .unwrap();
3084
3085 let start_val3 = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3087 let end_val3 = (3 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3088 let values_fragment3: Vec<i32> = (start_val3..end_val3).collect();
3089 let row_ids_fragment3: Vec<u64> = (start_val3 as u64..end_val3 as u64).collect();
3090 let fragment3_gen = gen_batch()
3091 .col("value", array::cycle::<Int32Type>(values_fragment3))
3092 .col("_rowid", array::cycle::<UInt64Type>(row_ids_fragment3))
3093 .into_df_stream(RowCount::from(fragment_size), BatchCount::from(1));
3094 let fragment3_data_source = Box::pin(RecordBatchStreamAdapter::new(
3095 fragment3_gen.schema(),
3096 fragment3_gen,
3097 ));
3098
3099 train_btree_index(
3100 fragment3_data_source,
3101 fragment_store.as_ref(),
3102 DEFAULT_BTREE_BATCH_SIZE,
3103 Some(vec![3]),
3104 None,
3105 )
3106 .await
3107 .unwrap();
3108
3109 let part_page_files = vec![
3111 part_page_data_file_path(1 << 32),
3112 part_page_data_file_path(2 << 32),
3113 part_page_data_file_path(3 << 32),
3114 ];
3115
3116 let part_lookup_files = vec![
3117 part_lookup_file_path(1 << 32),
3118 part_lookup_file_path(2 << 32),
3119 part_lookup_file_path(3 << 32),
3120 ];
3121
3122 super::merge_metadata_files(
3123 fragment_store.as_ref(),
3124 &part_page_files,
3125 &part_lookup_files,
3126 Option::from(1usize),
3127 )
3128 .await
3129 .unwrap();
3130
3131 let full_index = BTreeIndex::load(full_store.clone(), None, &LanceCache::no_cache())
3133 .await
3134 .unwrap();
3135
3136 let merged_index = BTreeIndex::load(fragment_store.clone(), None, &LanceCache::no_cache())
3137 .await
3138 .unwrap();
3139
3140 let query_min = SargableQuery::Equals(ScalarValue::Int32(Some(0)));
3144 let full_result_min = full_index
3145 .search(&query_min, &NoOpMetricsCollector)
3146 .await
3147 .unwrap();
3148 let merged_result_min = merged_index
3149 .search(&query_min, &NoOpMetricsCollector)
3150 .await
3151 .unwrap();
3152 assert_eq!(
3153 full_result_min, merged_result_min,
3154 "Query for minimum value 0 failed"
3155 );
3156
3157 let max_val = (3 * DEFAULT_BTREE_BATCH_SIZE - 1) as i32;
3159 let query_max = SargableQuery::Equals(ScalarValue::Int32(Some(max_val)));
3160 let full_result_max = full_index
3161 .search(&query_max, &NoOpMetricsCollector)
3162 .await
3163 .unwrap();
3164 let merged_result_max = merged_index
3165 .search(&query_max, &NoOpMetricsCollector)
3166 .await
3167 .unwrap();
3168 assert_eq!(
3169 full_result_max, merged_result_max,
3170 "Query for maximum value {} failed",
3171 max_val
3172 );
3173
3174 let fragment1_last = (DEFAULT_BTREE_BATCH_SIZE - 1) as i32;
3176 let query_frag1_last = SargableQuery::Equals(ScalarValue::Int32(Some(fragment1_last)));
3177 let full_result_frag1_last = full_index
3178 .search(&query_frag1_last, &NoOpMetricsCollector)
3179 .await
3180 .unwrap();
3181 let merged_result_frag1_last = merged_index
3182 .search(&query_frag1_last, &NoOpMetricsCollector)
3183 .await
3184 .unwrap();
3185 assert_eq!(
3186 full_result_frag1_last, merged_result_frag1_last,
3187 "Query for fragment 1 last value {} failed",
3188 fragment1_last
3189 );
3190
3191 let fragment2_first = DEFAULT_BTREE_BATCH_SIZE as i32;
3193 let query_frag2_first = SargableQuery::Equals(ScalarValue::Int32(Some(fragment2_first)));
3194 let full_result_frag2_first = full_index
3195 .search(&query_frag2_first, &NoOpMetricsCollector)
3196 .await
3197 .unwrap();
3198 let merged_result_frag2_first = merged_index
3199 .search(&query_frag2_first, &NoOpMetricsCollector)
3200 .await
3201 .unwrap();
3202 assert_eq!(
3203 full_result_frag2_first, merged_result_frag2_first,
3204 "Query for fragment 2 first value {} failed",
3205 fragment2_first
3206 );
3207
3208 let fragment2_last = (2 * DEFAULT_BTREE_BATCH_SIZE - 1) as i32;
3210 let query_frag2_last = SargableQuery::Equals(ScalarValue::Int32(Some(fragment2_last)));
3211 let full_result_frag2_last = full_index
3212 .search(&query_frag2_last, &NoOpMetricsCollector)
3213 .await
3214 .unwrap();
3215 let merged_result_frag2_last = merged_index
3216 .search(&query_frag2_last, &NoOpMetricsCollector)
3217 .await
3218 .unwrap();
3219 assert_eq!(
3220 full_result_frag2_last, merged_result_frag2_last,
3221 "Query for fragment 2 last value {} failed",
3222 fragment2_last
3223 );
3224
3225 let fragment3_first = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3227 let query_frag3_first = SargableQuery::Equals(ScalarValue::Int32(Some(fragment3_first)));
3228 let full_result_frag3_first = full_index
3229 .search(&query_frag3_first, &NoOpMetricsCollector)
3230 .await
3231 .unwrap();
3232 let merged_result_frag3_first = merged_index
3233 .search(&query_frag3_first, &NoOpMetricsCollector)
3234 .await
3235 .unwrap();
3236 assert_eq!(
3237 full_result_frag3_first, merged_result_frag3_first,
3238 "Query for fragment 3 first value {} failed",
3239 fragment3_first
3240 );
3241
3242 let query_below_min = SargableQuery::Equals(ScalarValue::Int32(Some(-1)));
3246 let full_result_below = full_index
3247 .search(&query_below_min, &NoOpMetricsCollector)
3248 .await
3249 .unwrap();
3250 let merged_result_below = merged_index
3251 .search(&query_below_min, &NoOpMetricsCollector)
3252 .await
3253 .unwrap();
3254 assert_eq!(
3255 full_result_below, merged_result_below,
3256 "Query for value below minimum (-1) failed"
3257 );
3258
3259 let query_above_max = SargableQuery::Equals(ScalarValue::Int32(Some(max_val + 1)));
3261 let full_result_above = full_index
3262 .search(&query_above_max, &NoOpMetricsCollector)
3263 .await
3264 .unwrap();
3265 let merged_result_above = merged_index
3266 .search(&query_above_max, &NoOpMetricsCollector)
3267 .await
3268 .unwrap();
3269 assert_eq!(
3270 full_result_above,
3271 merged_result_above,
3272 "Query for value above maximum ({}) failed",
3273 max_val + 1
3274 );
3275
3276 let range_start = (DEFAULT_BTREE_BATCH_SIZE - 100) as i32;
3280 let range_end = (DEFAULT_BTREE_BATCH_SIZE + 100) as i32;
3281 let query_cross_frag = SargableQuery::Range(
3282 std::collections::Bound::Included(ScalarValue::Int32(Some(range_start))),
3283 std::collections::Bound::Excluded(ScalarValue::Int32(Some(range_end))),
3284 );
3285 let full_result_cross = full_index
3286 .search(&query_cross_frag, &NoOpMetricsCollector)
3287 .await
3288 .unwrap();
3289 let merged_result_cross = merged_index
3290 .search(&query_cross_frag, &NoOpMetricsCollector)
3291 .await
3292 .unwrap();
3293 assert_eq!(
3294 full_result_cross, merged_result_cross,
3295 "Cross-fragment range query [{}, {}] failed",
3296 range_start, range_end
3297 );
3298
3299 let single_frag_start = 100i32;
3301 let single_frag_end = 200i32;
3302 let query_single_frag = SargableQuery::Range(
3303 std::collections::Bound::Included(ScalarValue::Int32(Some(single_frag_start))),
3304 std::collections::Bound::Excluded(ScalarValue::Int32(Some(single_frag_end))),
3305 );
3306 let full_result_single = full_index
3307 .search(&query_single_frag, &NoOpMetricsCollector)
3308 .await
3309 .unwrap();
3310 let merged_result_single = merged_index
3311 .search(&query_single_frag, &NoOpMetricsCollector)
3312 .await
3313 .unwrap();
3314 assert_eq!(
3315 full_result_single, merged_result_single,
3316 "Single fragment range query [{}, {}] failed",
3317 single_frag_start, single_frag_end
3318 );
3319
3320 let large_range_start = 100i32;
3322 let large_range_end = (3 * DEFAULT_BTREE_BATCH_SIZE - 100) as i32;
3323 let query_large_range = SargableQuery::Range(
3324 std::collections::Bound::Included(ScalarValue::Int32(Some(large_range_start))),
3325 std::collections::Bound::Excluded(ScalarValue::Int32(Some(large_range_end))),
3326 );
3327 let full_result_large = full_index
3328 .search(&query_large_range, &NoOpMetricsCollector)
3329 .await
3330 .unwrap();
3331 let merged_result_large = merged_index
3332 .search(&query_large_range, &NoOpMetricsCollector)
3333 .await
3334 .unwrap();
3335 assert_eq!(
3336 full_result_large, merged_result_large,
3337 "Large range query [{}, {}] failed",
3338 large_range_start, large_range_end
3339 );
3340
3341 let lt_val = (DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
3345 let query_lt = SargableQuery::Range(
3346 std::collections::Bound::Included(ScalarValue::Int32(Some(0))),
3347 std::collections::Bound::Excluded(ScalarValue::Int32(Some(lt_val))),
3348 );
3349 let full_result_lt = full_index
3350 .search(&query_lt, &NoOpMetricsCollector)
3351 .await
3352 .unwrap();
3353 let merged_result_lt = merged_index
3354 .search(&query_lt, &NoOpMetricsCollector)
3355 .await
3356 .unwrap();
3357 assert_eq!(
3358 full_result_lt, merged_result_lt,
3359 "Less than query (<{}) failed",
3360 lt_val
3361 );
3362
3363 let gt_val = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3365 let max_range_val = (3 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3366 let query_gt = SargableQuery::Range(
3367 std::collections::Bound::Excluded(ScalarValue::Int32(Some(gt_val))),
3368 std::collections::Bound::Excluded(ScalarValue::Int32(Some(max_range_val))),
3369 );
3370 let full_result_gt = full_index
3371 .search(&query_gt, &NoOpMetricsCollector)
3372 .await
3373 .unwrap();
3374 let merged_result_gt = merged_index
3375 .search(&query_gt, &NoOpMetricsCollector)
3376 .await
3377 .unwrap();
3378 assert_eq!(
3379 full_result_gt, merged_result_gt,
3380 "Greater than query (>{}) failed",
3381 gt_val
3382 );
3383
3384 let lte_val = (DEFAULT_BTREE_BATCH_SIZE - 1) as i32;
3386 let query_lte = SargableQuery::Range(
3387 std::collections::Bound::Included(ScalarValue::Int32(Some(0))),
3388 std::collections::Bound::Included(ScalarValue::Int32(Some(lte_val))),
3389 );
3390 let full_result_lte = full_index
3391 .search(&query_lte, &NoOpMetricsCollector)
3392 .await
3393 .unwrap();
3394 let merged_result_lte = merged_index
3395 .search(&query_lte, &NoOpMetricsCollector)
3396 .await
3397 .unwrap();
3398 assert_eq!(
3399 full_result_lte, merged_result_lte,
3400 "Less than or equal query (<={}) failed",
3401 lte_val
3402 );
3403
3404 let gte_val = (2 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3406 let query_gte = SargableQuery::Range(
3407 std::collections::Bound::Included(ScalarValue::Int32(Some(gte_val))),
3408 std::collections::Bound::Excluded(ScalarValue::Int32(Some(max_range_val))),
3409 );
3410 let full_result_gte = full_index
3411 .search(&query_gte, &NoOpMetricsCollector)
3412 .await
3413 .unwrap();
3414 let merged_result_gte = merged_index
3415 .search(&query_gte, &NoOpMetricsCollector)
3416 .await
3417 .unwrap();
3418 assert_eq!(
3419 full_result_gte, merged_result_gte,
3420 "Greater than or equal query (>={}) failed",
3421 gte_val
3422 );
3423 }
3424
3425 #[test]
3426 fn test_extract_partition_id() {
3427 assert_eq!(
3429 super::extract_partition_id("part_123_page_data.lance").unwrap(),
3430 123
3431 );
3432 assert_eq!(
3433 super::extract_partition_id("part_456_page_lookup.lance").unwrap(),
3434 456
3435 );
3436 assert_eq!(
3437 super::extract_partition_id("part_4294967296_page_data.lance").unwrap(),
3438 4294967296
3439 );
3440
3441 assert!(super::extract_partition_id("invalid_filename.lance").is_err());
3443 assert!(super::extract_partition_id("part_abc_page_data.lance").is_err());
3444 assert!(super::extract_partition_id("part_123").is_err());
3445 assert!(super::extract_partition_id("part_").is_err());
3446 }
3447
3448 #[tokio::test]
3449 async fn test_cleanup_partition_files() {
3450 let tmpdir = TempObjDir::default();
3452 let test_store: Arc<dyn crate::scalar::IndexStore> = Arc::new(LanceIndexStore::new(
3453 Arc::new(ObjectStore::local()),
3454 tmpdir.clone(),
3455 Arc::new(LanceCache::no_cache()),
3456 ));
3457
3458 let lookup_files = vec![
3460 "part_123_page_lookup.lance".to_string(),
3461 "invalid_lookup_file.lance".to_string(),
3462 "part_456_page_lookup.lance".to_string(),
3463 ];
3464
3465 let page_files = vec![
3466 "part_123_page_data.lance".to_string(),
3467 "invalid_page_file.lance".to_string(),
3468 "part_456_page_data.lance".to_string(),
3469 ];
3470
3471 super::cleanup_partition_files(test_store.as_ref(), &lookup_files, &page_files).await;
3474 }
3475
3476 #[tokio::test]
3477 async fn test_btree_null_handling_in_queries() {
3478 let store = Arc::new(LanceIndexStore::new(
3479 Arc::new(ObjectStore::memory()),
3480 Path::default(),
3481 Arc::new(LanceCache::no_cache()),
3482 ));
3483
3484 let batch = record_batch!(
3487 ("value", Int32, [None, Some(0), Some(5)]),
3488 ("_rowid", UInt64, [0, 1, 2])
3489 )
3490 .unwrap();
3491 let stream = stream::once(futures::future::ok(batch.clone()));
3492 let stream = Box::pin(RecordBatchStreamAdapter::new(batch.schema(), stream));
3493
3494 super::train_btree_index(stream, store.as_ref(), 256, None, None)
3496 .await
3497 .unwrap();
3498
3499 let cache = LanceCache::with_capacity(1024 * 1024);
3500 let index = super::BTreeIndex::load(store.clone(), None, &cache)
3501 .await
3502 .unwrap();
3503
3504 let query = SargableQuery::Equals(ScalarValue::Int32(Some(5)));
3506 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
3507
3508 match result {
3509 SearchResult::Exact(row_ids) => {
3510 let actual_rows: Vec<u64> = row_ids
3511 .true_rows()
3512 .row_addrs()
3513 .unwrap()
3514 .map(u64::from)
3515 .collect();
3516 assert_eq!(actual_rows, vec![2], "Should find row 2 where value == 5");
3517
3518 let null_row_ids = row_ids.null_rows();
3520 assert!(!null_row_ids.is_empty(), "null_row_ids should be non-empty");
3521 let null_rows: Vec<u64> =
3522 null_row_ids.row_addrs().unwrap().map(u64::from).collect();
3523 assert_eq!(null_rows, vec![0], "Should report row 0 as null");
3524 }
3525 _ => panic!("Expected Exact search result"),
3526 }
3527
3528 let query = SargableQuery::Range(
3530 std::ops::Bound::Included(ScalarValue::Int32(Some(0))),
3531 std::ops::Bound::Included(ScalarValue::Int32(Some(3))),
3532 );
3533 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
3534
3535 match result {
3536 SearchResult::Exact(row_ids) => {
3537 let actual_rows: Vec<u64> = row_ids
3538 .true_rows()
3539 .row_addrs()
3540 .unwrap()
3541 .map(u64::from)
3542 .collect();
3543 assert_eq!(actual_rows, vec![1], "Should find row 1 where value == 0");
3544
3545 let null_row_ids = row_ids.null_rows();
3547 assert!(!null_row_ids.is_empty(), "null_row_ids should be non-empty");
3548 let null_rows: Vec<u64> =
3549 null_row_ids.row_addrs().unwrap().map(u64::from).collect();
3550 assert_eq!(null_rows, vec![0], "Should report row 0 as null");
3551 }
3552 _ => panic!("Expected Exact search result"),
3553 }
3554
3555 let query = SargableQuery::IsIn(vec![
3557 ScalarValue::Int32(Some(0)),
3558 ScalarValue::Int32(Some(5)),
3559 ]);
3560 let result = index.search(&query, &NoOpMetricsCollector).await.unwrap();
3561
3562 match result {
3563 SearchResult::Exact(row_ids) => {
3564 let mut actual_rows: Vec<u64> = row_ids
3565 .true_rows()
3566 .row_addrs()
3567 .unwrap()
3568 .map(u64::from)
3569 .collect();
3570 actual_rows.sort();
3571 assert_eq!(
3572 actual_rows,
3573 vec![1, 2],
3574 "Should find rows 1 and 2 where value in [0, 5]"
3575 );
3576
3577 let null_row_ids = row_ids.null_rows();
3579 assert!(!null_row_ids.is_empty(), "null_row_ids should be non-empty");
3580 let null_rows: Vec<u64> =
3581 null_row_ids.row_addrs().unwrap().map(u64::from).collect();
3582 assert_eq!(null_rows, vec![0], "Should report row 0 as null");
3583 }
3584 _ => panic!("Expected Exact search result"),
3585 }
3586 }
3587
3588 #[tokio::test]
3589 async fn test_range_btree_index_consistency() {
3590 let full_tmpdir = TempObjDir::default();
3592 let full_store = Arc::new(LanceIndexStore::new(
3593 Arc::new(ObjectStore::local()),
3594 full_tmpdir.clone(),
3595 Arc::new(LanceCache::no_cache()),
3596 ));
3597
3598 let range_tmpdir = TempObjDir::default();
3599 let range_store = Arc::new(LanceIndexStore::new(
3600 Arc::new(ObjectStore::local()),
3601 range_tmpdir.clone(),
3602 Arc::new(LanceCache::no_cache()),
3603 ));
3604
3605 let total_count = 4 * DEFAULT_BTREE_BATCH_SIZE;
3608 let full_data_gen = gen_batch()
3609 .col("value", array::step::<Int32Type>())
3610 .col("_rowid", array::step::<UInt64Type>())
3611 .into_df_stream(RowCount::from(total_count / 4), BatchCount::from(4));
3612 let full_data_source = Box::pin(RecordBatchStreamAdapter::new(
3613 full_data_gen.schema(),
3614 full_data_gen,
3615 ));
3616
3617 train_btree_index(
3618 full_data_source,
3619 full_store.as_ref(),
3620 DEFAULT_BTREE_BATCH_SIZE,
3621 None,
3622 None,
3623 )
3624 .await
3625 .unwrap();
3626
3627 let range1_gen = gen_batch()
3630 .col("value", array::step::<Int32Type>())
3631 .col("_rowid", array::step::<UInt64Type>())
3632 .into_df_stream(
3633 RowCount::from(DEFAULT_BTREE_BATCH_SIZE / 2),
3634 BatchCount::from(5),
3635 );
3636 let range1_data_source = Box::pin(RecordBatchStreamAdapter::new(
3637 range1_gen.schema(),
3638 range1_gen,
3639 ));
3640
3641 train_btree_index(
3642 range1_data_source,
3643 range_store.as_ref(),
3644 DEFAULT_BTREE_BATCH_SIZE,
3645 None,
3646 Option::from(0u32),
3647 )
3648 .await
3649 .unwrap();
3650
3651 let start_val = (DEFAULT_BTREE_BATCH_SIZE * 2 + DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
3653 let end_val = (4 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3654 let values_second_half: Vec<i32> = (start_val..end_val).collect();
3655 let row_ids_second_half: Vec<u64> = (start_val as u64..end_val as u64).collect();
3656 let range2_gen = gen_batch()
3657 .col("value", array::cycle::<Int32Type>(values_second_half))
3658 .col("_rowid", array::cycle::<UInt64Type>(row_ids_second_half))
3659 .into_df_stream(
3660 RowCount::from(DEFAULT_BTREE_BATCH_SIZE / 2),
3661 BatchCount::from(3),
3662 );
3663 let range2_data_source = Box::pin(RecordBatchStreamAdapter::new(
3664 range2_gen.schema(),
3665 range2_gen,
3666 ));
3667
3668 train_btree_index(
3669 range2_data_source,
3670 range_store.as_ref(),
3671 DEFAULT_BTREE_BATCH_SIZE,
3672 None,
3673 Option::from(1u32),
3674 )
3675 .await
3676 .unwrap();
3677
3678 let part_page_files = vec![
3680 part_page_data_file_path(0 << 32),
3681 part_page_data_file_path(1 << 32),
3682 ];
3683
3684 let part_lookup_files = vec![
3685 part_lookup_file_path(0 << 32),
3686 part_lookup_file_path(1 << 32),
3687 ];
3688
3689 super::merge_metadata_files(
3690 range_store.as_ref(),
3691 &part_page_files,
3692 &part_lookup_files,
3693 Option::from(1usize),
3694 )
3695 .await
3696 .unwrap();
3697
3698 let full_index = BTreeIndex::load(full_store.clone(), None, &LanceCache::no_cache())
3699 .await
3700 .unwrap();
3701
3702 let ranged_index = BTreeIndex::load(range_store.clone(), None, &LanceCache::no_cache())
3703 .await
3704 .unwrap();
3705
3706 let query_0 = SargableQuery::Equals(ScalarValue::Int32(Some(0)));
3710 let full_result_0 = full_index
3711 .search(&query_0, &NoOpMetricsCollector)
3712 .await
3713 .unwrap();
3714 let ranged_result_0 = ranged_index
3715 .search(&query_0, &NoOpMetricsCollector)
3716 .await
3717 .unwrap();
3718 assert_eq!(full_result_0, ranged_result_0, "Query for value 0 failed");
3719
3720 let mid_first_batch = (DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
3722 let query_mid_first = SargableQuery::Equals(ScalarValue::Int32(Some(mid_first_batch)));
3723 let full_result_mid_first = full_index
3724 .search(&query_mid_first, &NoOpMetricsCollector)
3725 .await
3726 .unwrap();
3727 let ranged_result_mid_first = ranged_index
3728 .search(&query_mid_first, &NoOpMetricsCollector)
3729 .await
3730 .unwrap();
3731 assert_eq!(
3732 full_result_mid_first, ranged_result_mid_first,
3733 "Query for value {} failed",
3734 mid_first_batch
3735 );
3736
3737 let mid_last_batch = (DEFAULT_BTREE_BATCH_SIZE * 3 + (DEFAULT_BTREE_BATCH_SIZE / 2)) as i32;
3739 let query_mid_last = SargableQuery::Equals(ScalarValue::Int32(Some(mid_last_batch)));
3740 let full_result_mid_last = full_index
3741 .search(&query_mid_last, &NoOpMetricsCollector)
3742 .await
3743 .unwrap();
3744 let ranged_result_mid_last = ranged_index
3745 .search(&query_mid_last, &NoOpMetricsCollector)
3746 .await
3747 .unwrap();
3748 assert_eq!(
3749 full_result_mid_last, ranged_result_mid_last,
3750 "Query for value {} failed",
3751 mid_last_batch
3752 );
3753
3754 let max_val = (4 * DEFAULT_BTREE_BATCH_SIZE - 1) as i32;
3756 let query_max = SargableQuery::Equals(ScalarValue::Int32(Some(max_val)));
3757 let full_result_max = full_index
3758 .search(&query_max, &NoOpMetricsCollector)
3759 .await
3760 .unwrap();
3761 let ranged_result_max = ranged_index
3762 .search(&query_max, &NoOpMetricsCollector)
3763 .await
3764 .unwrap();
3765 assert_eq!(
3766 full_result_max, ranged_result_max,
3767 "Query for maximum value {} failed",
3768 max_val
3769 );
3770
3771 let second_first_val = (DEFAULT_BTREE_BATCH_SIZE * 2 + DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
3773 let query_second_first = SargableQuery::Equals(ScalarValue::Int32(Some(second_first_val)));
3774 let full_result_second_first = full_index
3775 .search(&query_second_first, &NoOpMetricsCollector)
3776 .await
3777 .unwrap();
3778 let ranged_result_second_first = ranged_index
3779 .search(&query_second_first, &NoOpMetricsCollector)
3780 .await
3781 .unwrap();
3782 assert_eq!(
3783 full_result_second_first, ranged_result_second_first,
3784 "Query for first value of the second page file {} failed",
3785 second_first_val
3786 );
3787
3788 let query_below_min = SargableQuery::Equals(ScalarValue::Int32(Some(-1)));
3790 let full_result_below = full_index
3791 .search(&query_below_min, &NoOpMetricsCollector)
3792 .await
3793 .unwrap();
3794 let ranged_result_below = ranged_index
3795 .search(&query_below_min, &NoOpMetricsCollector)
3796 .await
3797 .unwrap();
3798 assert_eq!(
3799 full_result_below, ranged_result_below,
3800 "Query for value below minimum (-1) failed"
3801 );
3802
3803 let query_above_max = SargableQuery::Equals(ScalarValue::Int32(Some(max_val + 1)));
3805 let full_result_above = full_index
3806 .search(&query_above_max, &NoOpMetricsCollector)
3807 .await
3808 .unwrap();
3809 let ranged_result_above = ranged_index
3810 .search(&query_above_max, &NoOpMetricsCollector)
3811 .await
3812 .unwrap();
3813 assert_eq!(
3814 full_result_above,
3815 ranged_result_above,
3816 "Query for value above maximum ({}) failed",
3817 max_val + 1
3818 );
3819
3820 let range_start =
3824 (DEFAULT_BTREE_BATCH_SIZE * 2 + DEFAULT_BTREE_BATCH_SIZE / 2 - 100) as i32;
3825 let range_end = range_start + 200;
3826 let query_cross_range = SargableQuery::Range(
3827 std::collections::Bound::Included(ScalarValue::Int32(Some(range_start))),
3828 std::collections::Bound::Excluded(ScalarValue::Int32(Some(range_end))),
3829 );
3830 let full_result_cross = full_index
3831 .search(&query_cross_range, &NoOpMetricsCollector)
3832 .await
3833 .unwrap();
3834 let ranged_result_cross = ranged_index
3835 .search(&query_cross_range, &NoOpMetricsCollector)
3836 .await
3837 .unwrap();
3838 assert_eq!(
3839 full_result_cross, ranged_result_cross,
3840 "Cross-range range query [{}, {}] failed",
3841 range_start, range_end
3842 );
3843
3844 let single_range_start = (DEFAULT_BTREE_BATCH_SIZE * 4 - 300) as i32;
3846 let single_range_end = single_range_start + 200;
3847 let query_single_range = SargableQuery::Range(
3848 std::collections::Bound::Included(ScalarValue::Int32(Some(single_range_start))),
3849 std::collections::Bound::Excluded(ScalarValue::Int32(Some(single_range_end))),
3850 );
3851 let full_result_single = full_index
3852 .search(&query_single_range, &NoOpMetricsCollector)
3853 .await
3854 .unwrap();
3855 let ranged_result_single = ranged_index
3856 .search(&query_single_range, &NoOpMetricsCollector)
3857 .await
3858 .unwrap();
3859 assert_eq!(
3860 full_result_single, ranged_result_single,
3861 "Single range query [{}, {}] failed",
3862 single_range_start, single_range_end
3863 );
3864
3865 let large_range_start = 100_i32;
3867 let large_range_end = (DEFAULT_BTREE_BATCH_SIZE * 4 - 100) as i32;
3868 let query_large_range = SargableQuery::Range(
3869 std::collections::Bound::Included(ScalarValue::Int32(Some(large_range_start))),
3870 std::collections::Bound::Excluded(ScalarValue::Int32(Some(large_range_end))),
3871 );
3872 let full_result_single = full_index
3873 .search(&query_large_range, &NoOpMetricsCollector)
3874 .await
3875 .unwrap();
3876 let ranged_result_single = ranged_index
3877 .search(&query_large_range, &NoOpMetricsCollector)
3878 .await
3879 .unwrap();
3880 assert_eq!(
3881 full_result_single, ranged_result_single,
3882 "Single fragment range query [{}, {}] failed",
3883 large_range_start, large_range_end
3884 );
3885
3886 let remap_dir = TempObjDir::default();
3887 let remap_store = Arc::new(LanceIndexStore::new(
3888 Arc::new(ObjectStore::local()),
3889 remap_dir.clone(),
3890 Arc::new(LanceCache::no_cache()),
3891 ));
3892
3893 ranged_index
3895 .remap(&HashMap::default(), remap_store.as_ref())
3896 .await
3897 .unwrap();
3898
3899 let remap_index = BTreeIndex::load(remap_store.clone(), None, &LanceCache::no_cache())
3900 .await
3901 .unwrap();
3902
3903 assert_eq!(remap_index.page_lookup, ranged_index.page_lookup);
3904
3905 let ranged_pages = range_store
3906 .open_index_file(part_page_data_file_path(1 << 32).as_str())
3907 .await
3908 .unwrap();
3909 let remapped_pages = remap_store
3910 .open_index_file(part_page_data_file_path(1 << 32).as_str())
3911 .await
3912 .unwrap();
3913
3914 assert_eq!(ranged_pages.num_rows(), remapped_pages.num_rows());
3915
3916 let original_data = ranged_pages
3917 .read_record_batch(0, ranged_pages.num_rows() as u64)
3918 .await
3919 .unwrap();
3920 let remapped_data = remapped_pages
3921 .read_record_batch(0, remapped_pages.num_rows() as u64)
3922 .await
3923 .unwrap();
3924
3925 assert_eq!(original_data, remapped_data);
3926 }
3927
3928 #[tokio::test]
3929 async fn test_update_ranged_index() {
3930 let old_tmpdir = TempObjDir::default();
3932 let old_store = Arc::new(LanceIndexStore::new(
3933 Arc::new(ObjectStore::local()),
3934 old_tmpdir.clone(),
3935 Arc::new(LanceCache::no_cache()),
3936 ));
3937
3938 let new_tmpdir = TempObjDir::default();
3939 let new_store = Arc::new(LanceIndexStore::new(
3940 Arc::new(ObjectStore::local()),
3941 new_tmpdir.clone(),
3942 Arc::new(LanceCache::no_cache()),
3943 ));
3944
3945 let range1_gen = gen_batch()
3947 .col("value", array::step::<Int32Type>())
3948 .col("_rowid", array::step::<UInt64Type>())
3949 .into_df_stream(
3950 RowCount::from(DEFAULT_BTREE_BATCH_SIZE / 2),
3951 BatchCount::from(5),
3952 );
3953 let range1_data_source = Box::pin(RecordBatchStreamAdapter::new(
3954 range1_gen.schema(),
3955 range1_gen,
3956 ));
3957
3958 train_btree_index(
3959 range1_data_source,
3960 old_store.as_ref(),
3961 DEFAULT_BTREE_BATCH_SIZE,
3962 None,
3963 Option::from(1u32),
3964 )
3965 .await
3966 .unwrap();
3967
3968 let start_val = (DEFAULT_BTREE_BATCH_SIZE * 2 + DEFAULT_BTREE_BATCH_SIZE / 2) as i32;
3970 let end_val = (4 * DEFAULT_BTREE_BATCH_SIZE) as i32;
3971 let values_second_half: Vec<i32> = (start_val..end_val).collect();
3972 let row_ids_second_half: Vec<u64> = (start_val as u64..end_val as u64).collect();
3973 let range2_gen = gen_batch()
3974 .col("value", array::cycle::<Int32Type>(values_second_half))
3975 .col("_rowid", array::cycle::<UInt64Type>(row_ids_second_half))
3976 .into_df_stream(
3977 RowCount::from(DEFAULT_BTREE_BATCH_SIZE / 2),
3978 BatchCount::from(3),
3979 );
3980 let range2_data_source = Box::pin(RecordBatchStreamAdapter::new(
3981 range2_gen.schema(),
3982 range2_gen,
3983 ));
3984
3985 train_btree_index(
3986 range2_data_source,
3987 old_store.as_ref(),
3988 DEFAULT_BTREE_BATCH_SIZE,
3989 None,
3990 Option::from(2u32),
3991 )
3992 .await
3993 .unwrap();
3994
3995 let part_page_files = vec![
3997 part_page_data_file_path(1 << 32),
3998 part_page_data_file_path(2 << 32),
3999 ];
4000
4001 let part_lookup_files = vec![
4002 part_lookup_file_path(1 << 32),
4003 part_lookup_file_path(2 << 32),
4004 ];
4005
4006 super::merge_metadata_files(
4007 old_store.as_ref(),
4008 &part_page_files,
4009 &part_lookup_files,
4010 Option::from(1usize),
4011 )
4012 .await
4013 .unwrap();
4014
4015 let start_val = (DEFAULT_BTREE_BATCH_SIZE * 2) as i32;
4017 let end_val = (DEFAULT_BTREE_BATCH_SIZE * 3) as i32;
4018 let row_id_delta = (DEFAULT_BTREE_BATCH_SIZE * 3) as i32;
4019 let values: Vec<i32> = (start_val..end_val).collect();
4020 let row_ids: Vec<u64> =
4021 ((start_val + row_id_delta) as u64..(end_val + row_id_delta) as u64).collect();
4022 let update_data = gen_batch()
4023 .col("value", array::cycle::<Int32Type>(values))
4024 .col("_rowid", array::cycle::<UInt64Type>(row_ids))
4025 .into_df_stream(
4026 RowCount::from(DEFAULT_BTREE_BATCH_SIZE / 2),
4027 BatchCount::from(2),
4028 );
4029 let update_data_source = Box::pin(RecordBatchStreamAdapter::new(
4030 update_data.schema(),
4031 update_data,
4032 ));
4033
4034 let ranged_index = BTreeIndex::load(old_store.clone(), None, &LanceCache::no_cache())
4035 .await
4036 .unwrap();
4037
4038 ranged_index
4040 .update(update_data_source, new_store.as_ref(), None)
4041 .await
4042 .expect("Error in updating ranged index");
4043
4044 let updated_index = BTreeIndex::load(new_store.clone(), None, &LanceCache::no_cache())
4045 .await
4046 .unwrap();
4047
4048 assert!(
4049 updated_index.ranges_to_files.is_none(),
4050 "Updated ranged-btree-index should fall back to non-ranged"
4051 );
4052
4053 let updated_value = (DEFAULT_BTREE_BATCH_SIZE * 2 + (DEFAULT_BTREE_BATCH_SIZE / 2)) as i32;
4054 let updated_query = SargableQuery::Equals(ScalarValue::Int32(Some(updated_value)));
4055
4056 let query_result = updated_index
4057 .search(&updated_query, &NoOpMetricsCollector)
4058 .await
4059 .unwrap();
4060 match query_result {
4061 SearchResult::Exact(row_id_map) => {
4062 assert!(
4063 row_id_map.selected(updated_value as u64),
4064 "Updated index should contain original rowids."
4065 );
4066 assert!(
4067 row_id_map.selected((updated_value + row_id_delta) as u64),
4068 "Updated index should contain new rowids"
4069 );
4070 }
4071 _ => {
4072 panic!("Btree search result should always be Exact.");
4073 }
4074 }
4075 }
4076
4077 #[tokio::test]
4087 async fn test_btree_remap_big_deletions() {
4088 let tmpdir = TempObjDir::default();
4089 let test_store = Arc::new(LanceIndexStore::new(
4090 Arc::new(ObjectStore::local()),
4091 tmpdir.clone(),
4092 Arc::new(LanceCache::no_cache()),
4093 ));
4094
4095 let batch_size = 4096;
4098 let total_rows = 15000;
4099
4100 let stream = gen_batch()
4101 .col("value", array::step::<Int32Type>())
4102 .col("_rowid", array::step::<UInt64Type>())
4103 .into_df_stream(RowCount::from(total_rows), BatchCount::from(1));
4104
4105 train_btree_index(stream, test_store.as_ref(), batch_size, None, None)
4106 .await
4107 .unwrap();
4108
4109 let index = BTreeIndex::load(test_store.clone(), None, &LanceCache::no_cache())
4110 .await
4111 .unwrap();
4112
4113 let mut mapping: HashMap<u64, Option<u64>> = HashMap::new();
4119
4120 for old_id in 1001..10000 {
4122 mapping.insert(old_id, None);
4123 }
4124
4125 let mut new_id_counter = 100_000;
4126
4127 for old_id in (0..1000).chain(10000..15000) {
4129 let new_id = new_id_counter;
4130 new_id_counter += 1;
4131 mapping.insert(old_id, Some(new_id));
4132 }
4133
4134 let remap_dir = TempObjDir::default();
4135 let remap_store = Arc::new(LanceIndexStore::new(
4136 Arc::new(ObjectStore::local()),
4137 remap_dir.clone(),
4138 Arc::new(LanceCache::no_cache()),
4139 ));
4140
4141 index.remap(&mapping, remap_store.as_ref()).await.unwrap();
4143
4144 let remapped_index = BTreeIndex::load(remap_store.clone(), None, &LanceCache::no_cache())
4145 .await
4146 .unwrap();
4147
4148 let should_exist = vec![0, 500, 1000, 10000, 13000, 14000, 14999];
4152 for value in should_exist {
4153 let query = SargableQuery::Equals(ScalarValue::Int32(Some(value)));
4154 let result = remapped_index
4155 .search(&query, &NoOpMetricsCollector)
4156 .await
4157 .unwrap();
4158 match result {
4159 SearchResult::Exact(row_id_map) => {
4160 assert!(
4161 !row_id_map.is_empty(),
4162 "Value {} should exist in remapped index but was not found",
4163 value
4164 );
4165 }
4166 _ => {
4167 panic!("Btree search result should always be Exact.");
4168 }
4169 }
4170 }
4171
4172 let should_not_exist = vec![1001, 5000, 8000, 9999];
4174 for value in should_not_exist {
4175 let query = SargableQuery::Equals(ScalarValue::Int32(Some(value)));
4176 let result = remapped_index
4177 .search(&query, &NoOpMetricsCollector)
4178 .await
4179 .unwrap();
4180 match result {
4181 SearchResult::Exact(row_id_map) => {
4182 assert!(
4183 row_id_map.is_empty(),
4184 "Value {} should NOT exist in remapped index but was found",
4185 value
4186 );
4187 }
4188 _ => {
4189 panic!("Btree search result should always be Exact.");
4190 }
4191 }
4192 }
4193 }
4194
4195 #[tokio::test]
4201 async fn test_search_tracks_nulls_for_absent_value() {
4202 use arrow_array::{Int32Array, UInt64Array};
4203
4204 let tmpdir = TempObjDir::default();
4205 let test_store = Arc::new(LanceIndexStore::new(
4206 Arc::new(ObjectStore::local()),
4207 tmpdir.clone(),
4208 Arc::new(LanceCache::no_cache()),
4209 ));
4210
4211 let num_rows = 5000u64;
4215 let values: Int32Array = (0..num_rows)
4216 .map(|i| {
4217 if i % 5 != 0 {
4218 None } else {
4220 Some(100 + i as i32) }
4222 })
4223 .collect();
4224 let row_ids = UInt64Array::from_iter_values(0..num_rows);
4225 let data = arrow_array::RecordBatch::try_from_iter(vec![
4226 ("value", Arc::new(values) as arrow_array::ArrayRef),
4227 ("_rowid", Arc::new(row_ids) as arrow_array::ArrayRef),
4228 ])
4229 .unwrap();
4230
4231 let schema = data.schema();
4232 let stream: SendableRecordBatchStream = Box::pin(RecordBatchStreamAdapter::new(
4233 schema,
4234 stream::iter(vec![Ok(data)]),
4235 ));
4236 train_btree_index(stream, test_store.as_ref(), num_rows, None, None)
4237 .await
4238 .unwrap();
4239
4240 let index = BTreeIndex::load(test_store.clone(), None, &LanceCache::no_cache())
4241 .await
4242 .unwrap();
4243
4244 assert!(
4246 !index.page_lookup.all_null_pages.is_empty(),
4247 "Test setup requires all-null pages; got null_pages={}, all_null_pages={}",
4248 index.page_lookup.null_pages.len(),
4249 index.page_lookup.all_null_pages.len(),
4250 );
4251
4252 let metrics = NoOpMetricsCollector;
4253
4254 let result = index
4256 .search(
4257 &SargableQuery::Equals(ScalarValue::Int32(Some(0))),
4258 &metrics,
4259 )
4260 .await
4261 .unwrap();
4262
4263 match result {
4264 SearchResult::Exact(set) => {
4265 assert!(set.true_rows().is_empty(), "No rows should match Equals(0)");
4267 assert!(
4269 !set.null_rows().is_empty(),
4270 "Null rows must be tracked even when no pages match the value"
4271 );
4272 }
4273 _ => panic!("BTree search should return Exact"),
4274 }
4275
4276 let result = index
4278 .search(
4279 &SargableQuery::Range(
4280 std::ops::Bound::Unbounded,
4281 std::ops::Bound::Excluded(ScalarValue::Int32(Some(50))),
4282 ),
4283 &metrics,
4284 )
4285 .await
4286 .unwrap();
4287
4288 match result {
4289 SearchResult::Exact(set) => {
4290 assert!(set.true_rows().is_empty(), "No rows should be < 50");
4291 assert!(
4292 !set.null_rows().is_empty(),
4293 "Null rows must be tracked for range queries too"
4294 );
4295 }
4296 _ => panic!("BTree search should return Exact"),
4297 }
4298 }
4299}