1use std::{fmt, sync::Arc};
5
6use arrow_array::{BooleanArray, RecordBatch, RecordBatchOptions, UInt64Array, make_array};
7use arrow_buffer::NullBuffer;
8use futures::{
9 FutureExt, Stream, StreamExt,
10 future::{BoxFuture, Shared},
11 stream::{BoxStream, FuturesOrdered},
12};
13use lance_arrow::RecordBatchExt;
14use lance_core::{
15 Error, ROW_ADDR, ROW_ADDR_FIELD, ROW_CREATED_AT_VERSION_FIELD, ROW_ID, ROW_ID_FIELD,
16 ROW_LAST_UPDATED_AT_VERSION_FIELD, Result,
17 utils::{address::RowAddress, deletion::DeletionVector},
18};
19use lance_io::ReadBatchParams;
20use tracing::instrument;
21
22use crate::rowids::RowIdSequence;
23
24pub type ReadBatchFut = BoxFuture<'static, Result<RecordBatch>>;
25pub struct ReadBatchTask {
28 pub task: ReadBatchFut,
29 pub num_rows: u32,
30}
31pub type ReadBatchTaskStream = BoxStream<'static, ReadBatchTask>;
32pub type ReadBatchFutStream = BoxStream<'static, ReadBatchFut>;
33
34type SharedReadBatchFut = Shared<BoxFuture<'static, std::result::Result<RecordBatch, Arc<Error>>>>;
35
36#[derive(Debug)]
37struct SharedReadError(Arc<Error>);
38
39impl fmt::Display for SharedReadError {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 self.0.fmt(f)
42 }
43}
44
45impl std::error::Error for SharedReadError {
46 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
47 Some(self.0.as_ref())
48 }
49}
50
51struct PendingReadBatch {
52 task: Option<ReadBatchFut>,
53 shared_task: Option<SharedReadBatchFut>,
54 offset: u32,
55 num_rows: u32,
56}
57
58impl PendingReadBatch {
59 fn new(task: ReadBatchTask) -> Self {
60 Self {
61 task: Some(task.task),
62 shared_task: None,
63 offset: 0,
64 num_rows: task.num_rows,
65 }
66 }
67
68 fn take(&mut self, num_rows: u32) -> ReadBatchFut {
69 debug_assert!(num_rows <= self.num_rows);
70
71 if self.offset == 0 && num_rows == self.num_rows && self.shared_task.is_none() {
72 self.num_rows = 0;
73 let Some(task) = self.task.take() else {
74 return async {
75 Err(Error::internal(
76 "missing read task while merging aligned streams".to_string(),
77 ))
78 }
79 .boxed();
80 };
81 return task;
82 }
83
84 let shared_task = self
85 .shared_task
86 .get_or_insert_with(|| {
87 let task = self.task.take();
88 async move {
89 let Some(task) = task else {
90 return Err(Arc::new(Error::internal(
91 "missing read task while splitting a merged stream".to_string(),
92 )));
93 };
94 task.await.map_err(Arc::new)
95 }
96 .boxed()
97 .shared()
98 })
99 .clone();
100 let offset = self.offset;
101 self.offset += num_rows;
102 self.num_rows -= num_rows;
103
104 async move {
105 match shared_task.await {
106 Ok(batch) => Ok(batch.slice(offset as usize, num_rows as usize)),
107 Err(error) => Err(Error::wrapped(Box::new(SharedReadError(error)))),
108 }
109 }
110 .boxed()
111 }
112}
113
114struct MergeStream {
115 streams: Vec<ReadBatchTaskStream>,
116 pending: Vec<Option<PendingReadBatch>>,
117 index: usize,
118}
119
120impl MergeStream {
121 fn emit(&mut self) -> ReadBatchTask {
122 let num_rows = self
123 .pending
124 .iter()
125 .filter_map(|pending| pending.as_ref().map(|pending| pending.num_rows))
126 .min()
127 .unwrap_or_default();
128 let mut batches = FuturesOrdered::new();
129 for pending in &mut self.pending {
130 let Some(pending_batch) = pending.as_mut() else {
131 continue;
132 };
133 batches.push_back(pending_batch.take(num_rows));
134 if pending_batch.num_rows == 0 {
135 *pending = None;
136 }
137 }
138 let task = async move {
139 let Some(first) = batches.next().await else {
140 return Err(Error::internal(
141 "cannot merge an empty set of read batches".to_string(),
142 ));
143 };
144 let mut batch = first?;
145 while let Some(next) = batches.next().await {
146 let next = next?;
147 batch = batch.merge(&next)?;
148 }
149 Ok(batch)
150 }
151 .boxed();
152 ReadBatchTask { task, num_rows }
153 }
154}
155
156impl Stream for MergeStream {
157 type Item = ReadBatchTask;
158
159 fn poll_next(
160 mut self: std::pin::Pin<&mut Self>,
161 cx: &mut std::task::Context<'_>,
162 ) -> std::task::Poll<Option<Self::Item>> {
163 loop {
164 if self.pending.iter().all(Option::is_some) {
165 return std::task::Poll::Ready(Some(self.emit()));
166 }
167
168 let index = self.index;
169 if self.pending[index].is_some() {
170 self.index = (index + 1) % self.streams.len();
171 continue;
172 }
173 match self.streams[index].poll_next_unpin(cx) {
174 std::task::Poll::Ready(Some(batch_task)) => {
175 self.pending[index] = Some(PendingReadBatch::new(batch_task));
176 self.index = (index + 1) % self.streams.len();
177 }
178 std::task::Poll::Ready(None) => {
179 return std::task::Poll::Ready(None);
180 }
181 std::task::Poll::Pending => {
182 return std::task::Poll::Pending;
183 }
184 }
185 }
186 }
187}
188
189pub fn merge_streams(streams: Vec<ReadBatchTaskStream>) -> ReadBatchTaskStream {
200 if streams.is_empty() {
201 return futures::stream::empty().boxed();
202 }
203 let pending = (0..streams.len()).map(|_| None).collect();
204 MergeStream {
205 streams,
206 pending,
207 index: 0,
208 }
209 .boxed()
210}
211
212fn apply_deletions_as_nulls(batch: RecordBatch, mask: &BooleanArray) -> Result<RecordBatch> {
219 let mask_buffer = NullBuffer::new(mask.values().clone());
223
224 if mask_buffer.null_count() == 0 {
225 return Ok(batch);
227 }
228
229 let new_columns = batch
231 .schema()
232 .fields()
233 .iter()
234 .zip(batch.columns())
235 .map(|(field, col)| {
236 if field.name() == ROW_ID || field.name() == ROW_ADDR {
237 let col_data = col.to_data();
238 let null_buffer = NullBuffer::union(col_data.nulls(), Some(&mask_buffer));
241
242 Ok(col_data
243 .into_builder()
244 .null_bit_buffer(null_buffer.map(|b| b.buffer().clone()))
245 .build()
246 .map(make_array)?)
247 } else {
248 Ok(col.clone())
249 }
250 })
251 .collect::<Result<Vec<_>>>()?;
252
253 Ok(RecordBatch::try_new_with_options(
254 batch.schema(),
255 new_columns,
256 &RecordBatchOptions::new().with_row_count(Some(batch.num_rows())),
257 )?)
258}
259
260fn version_values_for_selection(
264 sequence: &crate::rowids::version::RowDatasetVersionSequence,
265 params: &ReadBatchParams,
266 batch_offset: u32,
267 num_rows: u32,
268) -> Result<Vec<u64>> {
269 let selection = params
270 .slice(batch_offset as usize, num_rows as usize)
271 .unwrap()
272 .to_ranges()
273 .unwrap();
274
275 if sequence.runs.len() == 1 {
276 return Ok(vec![sequence.runs[0].version(); num_rows as usize]);
277 }
278
279 let mut versions = Vec::with_capacity(num_rows as usize);
280 let run_offsets: Vec<usize> = sequence
281 .runs
282 .iter()
283 .scan(0usize, |acc, run| {
284 let start = *acc;
285 *acc += run.len();
286 Some(start)
287 })
288 .collect();
289 let total_len: usize = sequence.runs.iter().map(|r| r.len()).sum();
290
291 for r in &selection {
292 for pos in r.start..r.end {
293 let pos = pos as usize;
294 if pos >= total_len {
295 return Err(lance_core::Error::internal(format!(
296 "version column position {} out of range (total_len={})",
297 pos, total_len
298 )));
299 }
300 let run_idx = match run_offsets.binary_search(&pos) {
301 Ok(idx) => idx,
302 Err(idx) => idx - 1,
303 };
304 versions.push(sequence.runs[run_idx].version());
305 }
306 }
307 Ok(versions)
308}
309
310#[derive(Debug)]
312pub struct RowIdAndDeletesConfig {
313 pub params: ReadBatchParams,
315 pub with_row_id: bool,
317 pub with_row_addr: bool,
319 pub with_row_last_updated_at_version: bool,
321 pub with_row_created_at_version: bool,
323 pub deletion_vector: Option<Arc<DeletionVector>>,
325 pub row_id_sequence: Option<Arc<RowIdSequence>>,
327 pub last_updated_at_sequence: Option<Arc<crate::rowids::version::RowDatasetVersionSequence>>,
329 pub created_at_sequence: Option<Arc<crate::rowids::version::RowDatasetVersionSequence>>,
331 pub make_deletions_null: bool,
333 pub total_num_rows: u32,
337}
338
339impl RowIdAndDeletesConfig {
340 fn has_system_cols(&self) -> bool {
341 self.with_row_id
342 || self.with_row_addr
343 || self.with_row_last_updated_at_version
344 || self.with_row_created_at_version
345 }
346}
347
348#[instrument(level = "debug", skip_all)]
349pub fn apply_row_id_and_deletes(
350 batch: RecordBatch,
351 batch_offset: u32,
352 fragment_id: u32,
353 config: &RowIdAndDeletesConfig,
354) -> Result<RecordBatch> {
355 let mut deletion_vector = config.deletion_vector.as_ref();
356 if let Some(deletion_vector_inner) = deletion_vector
358 && matches!(deletion_vector_inner.as_ref(), DeletionVector::NoDeletions)
359 {
360 deletion_vector = None;
361 }
362 let has_deletions = deletion_vector.is_some();
363 debug_assert!(batch.num_columns() > 0 || config.has_system_cols() || has_deletions);
364
365 let should_fetch_row_addr = config.with_row_addr
367 || (config.with_row_id && config.row_id_sequence.is_none())
368 || has_deletions;
369
370 let num_rows = batch.num_rows() as u32;
371
372 let row_addrs =
373 if should_fetch_row_addr {
374 let _rowaddrs = tracing::span!(tracing::Level::DEBUG, "fetch_row_addrs").entered();
375 let mut row_addrs = Vec::with_capacity(num_rows as usize);
376 for offset_range in config
377 .params
378 .slice(batch_offset as usize, num_rows as usize)
379 .unwrap()
380 .iter_offset_ranges()?
381 {
382 row_addrs.extend(offset_range.map(|row_offset| {
383 u64::from(RowAddress::new_from_parts(fragment_id, row_offset))
384 }));
385 }
386
387 Some(Arc::new(UInt64Array::from(row_addrs)))
388 } else {
389 None
390 };
391
392 let row_ids = if config.with_row_id {
393 let _rowids = tracing::span!(tracing::Level::DEBUG, "fetch_row_ids").entered();
394 if let Some(row_id_sequence) = &config.row_id_sequence {
395 let selection = config
396 .params
397 .slice(batch_offset as usize, num_rows as usize)
398 .unwrap()
399 .to_ranges()
400 .unwrap();
401 let row_ids = row_id_sequence
402 .select(
403 selection
404 .iter()
405 .flat_map(|r| r.start as usize..r.end as usize),
406 )
407 .collect::<UInt64Array>();
408 Some(Arc::new(row_ids))
409 } else {
410 row_addrs.clone()
413 }
414 } else {
415 None
416 };
417
418 let span = tracing::span!(tracing::Level::DEBUG, "apply_deletions");
419 let _enter = span.enter();
420 let deletion_mask = deletion_vector.and_then(|v| {
421 let row_addrs: &[u64] = row_addrs.as_ref().unwrap().values();
422 v.build_predicate(row_addrs.iter())
423 });
424
425 let batch = if config.with_row_id {
426 let row_id_arr = row_ids.unwrap();
427 batch.try_with_column(ROW_ID_FIELD.clone(), row_id_arr)?
428 } else {
429 batch
430 };
431
432 let batch = if config.with_row_addr {
433 let row_addr_arr = row_addrs.unwrap();
434 batch.try_with_column(ROW_ADDR_FIELD.clone(), row_addr_arr)?
435 } else {
436 batch
437 };
438
439 let batch = if config.with_row_last_updated_at_version || config.with_row_created_at_version {
441 let mut batch = batch;
442
443 if config.with_row_last_updated_at_version {
444 let version_arr = if let Some(sequence) = &config.last_updated_at_sequence {
445 Arc::new(UInt64Array::from(version_values_for_selection(
446 sequence,
447 &config.params,
448 batch_offset,
449 num_rows,
450 )?))
451 } else {
452 Arc::new(UInt64Array::from(vec![1u64; num_rows as usize]))
454 };
455 batch =
456 batch.try_with_column(ROW_LAST_UPDATED_AT_VERSION_FIELD.clone(), version_arr)?;
457 }
458
459 if config.with_row_created_at_version {
460 let version_arr = if let Some(sequence) = &config.created_at_sequence {
461 Arc::new(UInt64Array::from(version_values_for_selection(
462 sequence,
463 &config.params,
464 batch_offset,
465 num_rows,
466 )?))
467 } else {
468 Arc::new(UInt64Array::from(vec![1u64; num_rows as usize]))
470 };
471 batch = batch.try_with_column(ROW_CREATED_AT_VERSION_FIELD.clone(), version_arr)?;
472 }
473
474 batch
475 } else {
476 batch
477 };
478
479 match (deletion_mask, config.make_deletions_null) {
480 (None, _) => Ok(batch),
481 (Some(mask), false) => Ok(arrow::compute::filter_record_batch(&batch, &mask)?),
482 (Some(mask), true) => Ok(apply_deletions_as_nulls(batch, &mask)?),
483 }
484}
485
486pub fn wrap_with_row_id_and_delete(
492 stream: ReadBatchTaskStream,
493 fragment_id: u32,
494 config: RowIdAndDeletesConfig,
495) -> ReadBatchFutStream {
496 let config = Arc::new(config);
497 let mut offset = 0;
498 stream
499 .map(move |batch_task| {
500 let config = config.clone();
501 let this_offset = offset;
502 let num_rows = batch_task.num_rows;
503 offset += num_rows;
504 batch_task
505 .task
506 .map(move |batch| {
507 apply_row_id_and_deletes(batch?, this_offset, fragment_id, config.as_ref())
508 })
509 .boxed()
510 })
511 .boxed()
512}
513
514#[cfg(test)]
515mod tests {
516 use std::sync::Arc;
517
518 use arrow::{array::AsArray, datatypes::UInt64Type};
519 use arrow_array::{RecordBatch, UInt32Array, types::Int32Type};
520 use arrow_schema::ArrowError;
521 use futures::{
522 FutureExt, StreamExt, TryStreamExt,
523 stream::{self, BoxStream},
524 };
525 use lance_core::{
526 ROW_ID,
527 utils::{address::RowAddress, deletion::DeletionVector},
528 };
529 use lance_datagen::{BatchCount, RowCount};
530 use lance_io::{ReadBatchParams, stream::arrow_stream_to_lance_stream};
531 use roaring::RoaringBitmap;
532
533 use crate::utils::stream::ReadBatchTask;
534
535 use super::RowIdAndDeletesConfig;
536
537 fn batch_task_stream(
538 datagen_stream: BoxStream<'static, std::result::Result<RecordBatch, ArrowError>>,
539 ) -> super::ReadBatchTaskStream {
540 arrow_stream_to_lance_stream(datagen_stream)
541 .map(|batch| ReadBatchTask {
542 num_rows: batch.as_ref().unwrap().num_rows() as u32,
543 task: std::future::ready(batch).boxed(),
544 })
545 .boxed()
546 }
547
548 #[tokio::test]
549 async fn test_basic_zip() {
550 let left = batch_task_stream(
551 lance_datagen::gen_batch()
552 .col("x", lance_datagen::array::step::<Int32Type>())
553 .into_reader_stream(RowCount::from(100), BatchCount::from(10))
554 .0,
555 );
556 let right = batch_task_stream(
557 lance_datagen::gen_batch()
558 .col("y", lance_datagen::array::step::<Int32Type>())
559 .into_reader_stream(RowCount::from(100), BatchCount::from(10))
560 .0,
561 );
562
563 let merged = super::merge_streams(vec![left, right])
564 .map(|batch_task| batch_task.task)
565 .buffered(1)
566 .try_collect::<Vec<_>>()
567 .await
568 .unwrap();
569
570 let expected = lance_datagen::gen_batch()
571 .col("x", lance_datagen::array::step::<Int32Type>())
572 .col("y", lance_datagen::array::step::<Int32Type>())
573 .into_reader_rows(RowCount::from(100), BatchCount::from(10))
574 .collect::<Result<Vec<_>, ArrowError>>()
575 .unwrap();
576 assert_eq!(merged, expected);
577 }
578
579 #[tokio::test]
580 async fn test_zip_with_different_batch_boundaries() {
581 let left_batch =
582 arrow_array::record_batch!(("x", Int32, (0..10).collect::<Vec<_>>())).unwrap();
583 let right_batch =
584 arrow_array::record_batch!(("y", Int32, (10..20).collect::<Vec<_>>())).unwrap();
585 let left = batch_task_stream(
586 stream::iter([Ok(left_batch.slice(0, 6)), Ok(left_batch.slice(6, 4))]).boxed(),
587 );
588 let right = batch_task_stream(
589 stream::iter([Ok(right_batch.slice(0, 4)), Ok(right_batch.slice(4, 6))]).boxed(),
590 );
591
592 let merged = super::merge_streams(vec![left, right])
593 .map(|batch_task| batch_task.task)
594 .buffered(3)
595 .try_collect::<Vec<_>>()
596 .await
597 .unwrap();
598
599 let expected = vec![
600 arrow_array::record_batch!(
601 ("x", Int32, (0..4).collect::<Vec<_>>()),
602 ("y", Int32, (10..14).collect::<Vec<_>>())
603 )
604 .unwrap(),
605 arrow_array::record_batch!(
606 ("x", Int32, (4..6).collect::<Vec<_>>()),
607 ("y", Int32, (14..16).collect::<Vec<_>>())
608 )
609 .unwrap(),
610 arrow_array::record_batch!(
611 ("x", Int32, (6..10).collect::<Vec<_>>()),
612 ("y", Int32, (16..20).collect::<Vec<_>>())
613 )
614 .unwrap(),
615 ];
616 assert_eq!(merged, expected);
617 }
618
619 async fn check_row_id(params: ReadBatchParams, expected: impl IntoIterator<Item = u32>) {
620 let expected = Vec::from_iter(expected);
621
622 for has_columns in [false, true] {
623 for fragment_id in [0, 10] {
624 let mut datagen = lance_datagen::gen_batch();
626 if has_columns {
627 datagen = datagen.col("x", lance_datagen::array::rand::<Int32Type>());
628 }
629 let data = batch_task_stream(
630 datagen
631 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
632 .0,
633 );
634
635 let config = RowIdAndDeletesConfig {
636 params: params.clone(),
637 with_row_id: true,
638 with_row_addr: false,
639 with_row_last_updated_at_version: false,
640 with_row_created_at_version: false,
641 deletion_vector: None,
642 row_id_sequence: None,
643 last_updated_at_sequence: None,
644 created_at_sequence: None,
645 make_deletions_null: false,
646 total_num_rows: 100,
647 };
648 let stream = super::wrap_with_row_id_and_delete(data, fragment_id, config);
649 let batches = stream.buffered(1).try_collect::<Vec<_>>().await.unwrap();
650
651 let mut offset = 0;
652 let expected = expected.clone();
653 for batch in batches {
654 let actual_row_ids =
655 batch[ROW_ID].as_primitive::<UInt64Type>().values().to_vec();
656 let expected_row_ids = expected[offset..offset + 10]
657 .iter()
658 .map(|row_offset| {
659 RowAddress::new_from_parts(fragment_id, *row_offset).into()
660 })
661 .collect::<Vec<u64>>();
662 assert_eq!(actual_row_ids, expected_row_ids);
663 offset += batch.num_rows();
664 }
665 }
666 }
667 }
668
669 #[tokio::test]
670 async fn test_row_id() {
671 let some_indices = (0..100).rev().collect::<Vec<u32>>();
672 let some_indices_arr = UInt32Array::from(some_indices.clone());
673 check_row_id(ReadBatchParams::RangeFull, 0..100).await;
674 check_row_id(ReadBatchParams::Indices(some_indices_arr), some_indices).await;
675 check_row_id(ReadBatchParams::Range(1000..1100), 1000..1100).await;
676 check_row_id(
677 ReadBatchParams::RangeFrom(std::ops::RangeFrom { start: 1000 }),
678 1000..1100,
679 )
680 .await;
681 check_row_id(
682 ReadBatchParams::RangeTo(std::ops::RangeTo { end: 1000 }),
683 0..100,
684 )
685 .await;
686 }
687
688 #[tokio::test]
689 async fn test_deletes() {
690 let no_deletes: Option<Arc<DeletionVector>> = None;
691 let no_deletes_2 = Some(Arc::new(DeletionVector::NoDeletions));
692 let delete_some_bitmap = Some(Arc::new(DeletionVector::Bitmap(RoaringBitmap::from_iter(
693 0..35,
694 ))));
695 let delete_some_set = Some(Arc::new(DeletionVector::Set((0..35).collect())));
696
697 for deletion_vector in [
698 no_deletes,
699 no_deletes_2,
700 delete_some_bitmap,
701 delete_some_set,
702 ] {
703 for has_columns in [false, true] {
704 for with_row_id in [false, true] {
705 for make_deletions_null in [false, true] {
706 for frag_id in [0, 1] {
707 let has_deletions = if let Some(dv) = &deletion_vector {
708 !matches!(dv.as_ref(), DeletionVector::NoDeletions)
709 } else {
710 false
711 };
712 if !has_columns && !has_deletions && !with_row_id {
713 continue;
716 }
717 if make_deletions_null && !with_row_id {
718 continue;
721 }
722
723 let mut datagen = lance_datagen::gen_batch();
724 if has_columns {
725 datagen =
726 datagen.col("x", lance_datagen::array::rand::<Int32Type>());
727 }
728 let data = batch_task_stream(
730 datagen
731 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
732 .0,
733 );
734
735 let config = RowIdAndDeletesConfig {
736 params: ReadBatchParams::RangeFull,
737 with_row_id,
738 with_row_addr: false,
739 with_row_last_updated_at_version: false,
740 with_row_created_at_version: false,
741 deletion_vector: deletion_vector.clone(),
742 row_id_sequence: None,
743 last_updated_at_sequence: None,
744 created_at_sequence: None,
745 make_deletions_null,
746 total_num_rows: 100,
747 };
748 let stream = super::wrap_with_row_id_and_delete(data, frag_id, config);
749 let batches = stream
750 .buffered(1)
751 .filter_map(|batch| {
752 std::future::ready(
753 batch
754 .map(|batch| {
755 if batch.num_rows() == 0 {
756 None
757 } else {
758 Some(batch)
759 }
760 })
761 .transpose(),
762 )
763 })
764 .try_collect::<Vec<_>>()
765 .await
766 .unwrap();
767
768 let total_num_rows =
769 batches.iter().map(|b| b.num_rows()).sum::<usize>();
770 let total_num_nulls = if make_deletions_null {
771 batches
772 .iter()
773 .map(|b| b[ROW_ID].null_count())
774 .sum::<usize>()
775 } else {
776 0
777 };
778 let total_actually_deleted = total_num_nulls + (100 - total_num_rows);
779
780 let expected_deletions = match &deletion_vector {
781 None => 0,
782 Some(deletion_vector) => match deletion_vector.as_ref() {
783 DeletionVector::NoDeletions => 0,
784 DeletionVector::Bitmap(b) => b.len() as usize,
785 DeletionVector::Set(s) => s.len(),
786 },
787 };
788 assert_eq!(total_actually_deleted, expected_deletions);
789 if expected_deletions > 0 && with_row_id {
790 if make_deletions_null {
791 assert_eq!(
794 batches[3][ROW_ID].as_primitive::<UInt64Type>().value(0),
795 u64::from(RowAddress::new_from_parts(frag_id, 30))
796 );
797 assert_eq!(batches[3][ROW_ID].null_count(), 5);
798 } else {
799 assert_eq!(
801 batches[0][ROW_ID].as_primitive::<UInt64Type>().value(0),
802 u64::from(RowAddress::new_from_parts(frag_id, 35))
803 );
804 }
805 }
806 if !with_row_id {
807 assert!(batches[0].column_by_name(ROW_ID).is_none());
808 }
809 }
810 }
811 }
812 }
813 }
814 }
815
816 #[tokio::test]
817 async fn test_version_column_with_deletions() {
818 use crate::rowids::segment::U64Segment;
819 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
820
821 let seq = Arc::new(RowDatasetVersionSequence {
822 runs: vec![RowDatasetVersionRun {
823 span: U64Segment::Range(0..100),
824 version: 42,
825 }],
826 });
827
828 let data = batch_task_stream(
829 lance_datagen::gen_batch()
830 .col("x", lance_datagen::array::rand::<Int32Type>())
831 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
832 .0,
833 );
834
835 let config = RowIdAndDeletesConfig {
836 params: ReadBatchParams::RangeFull,
837 with_row_id: true,
838 with_row_addr: false,
839 with_row_last_updated_at_version: false,
840 with_row_created_at_version: true,
841 deletion_vector: Some(Arc::new(DeletionVector::Bitmap(RoaringBitmap::from_iter(
842 0..35,
843 )))),
844 row_id_sequence: None,
845 last_updated_at_sequence: None,
846 created_at_sequence: Some(seq),
847 make_deletions_null: false,
848 total_num_rows: 100,
849 };
850 let stream = super::wrap_with_row_id_and_delete(data, 0, config);
851 let batches: Vec<_> = stream
852 .buffered(1)
853 .try_filter(|b| std::future::ready(b.num_rows() > 0))
854 .try_collect()
855 .await
856 .unwrap();
857
858 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
859 assert_eq!(total_rows, 65);
860
861 for batch in &batches {
862 let versions = batch
863 .column_by_name("_row_created_at_version")
864 .unwrap()
865 .as_primitive::<UInt64Type>()
866 .values();
867 assert!(versions.iter().all(|&v| v == 42));
868 }
869 }
870
871 #[tokio::test]
872 async fn test_version_column_multi_run() {
873 use crate::rowids::segment::U64Segment;
874 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
875
876 let seq = Arc::new(RowDatasetVersionSequence {
878 runs: vec![
879 RowDatasetVersionRun {
880 span: U64Segment::Range(0..40),
881 version: 1,
882 },
883 RowDatasetVersionRun {
884 span: U64Segment::Range(40..70),
885 version: 2,
886 },
887 RowDatasetVersionRun {
888 span: U64Segment::Range(70..100),
889 version: 3,
890 },
891 ],
892 });
893
894 let mut deletions = RoaringBitmap::from_iter(0..20);
897 deletions.extend(60..80);
898
899 let data = batch_task_stream(
900 lance_datagen::gen_batch()
901 .col("x", lance_datagen::array::rand::<Int32Type>())
902 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
903 .0,
904 );
905
906 let config = RowIdAndDeletesConfig {
907 params: ReadBatchParams::RangeFull,
908 with_row_id: true,
909 with_row_addr: false,
910 with_row_last_updated_at_version: false,
911 with_row_created_at_version: true,
912 deletion_vector: Some(Arc::new(DeletionVector::Bitmap(deletions))),
913 row_id_sequence: None,
914 last_updated_at_sequence: None,
915 created_at_sequence: Some(seq),
916 make_deletions_null: false,
917 total_num_rows: 100,
918 };
919 let stream = super::wrap_with_row_id_and_delete(data, 0, config);
920 let batches: Vec<_> = stream
921 .buffered(1)
922 .try_filter(|b| std::future::ready(b.num_rows() > 0))
923 .try_collect()
924 .await
925 .unwrap();
926
927 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
928 assert_eq!(total_rows, 60);
929
930 let all_versions: Vec<u64> = batches
931 .iter()
932 .flat_map(|b| {
933 b.column_by_name("_row_created_at_version")
934 .unwrap()
935 .as_primitive::<UInt64Type>()
936 .values()
937 .to_vec()
938 })
939 .collect();
940
941 assert!(all_versions[..20].iter().all(|&v| v == 1));
942 assert!(all_versions[20..40].iter().all(|&v| v == 2));
943 assert!(all_versions[40..60].iter().all(|&v| v == 3));
944 }
945}