1use std::{
5 fmt,
6 sync::{Arc, OnceLock},
7};
8
9use arrow_array::{
10 ArrayRef, BooleanArray, RecordBatch, RecordBatchOptions, UInt64Array, make_array,
11};
12use arrow_buffer::NullBuffer;
13use arrow_schema::{Field, Schema, SchemaRef};
14use futures::{
15 FutureExt, Stream, StreamExt,
16 future::{BoxFuture, Shared},
17 stream::{BoxStream, FuturesOrdered},
18};
19use lance_arrow::RecordBatchExt;
20use lance_core::{
21 Error, ROW_ADDR, ROW_ADDR_FIELD, ROW_CREATED_AT_VERSION_FIELD, ROW_ID, ROW_ID_FIELD,
22 ROW_LAST_UPDATED_AT_VERSION_FIELD, Result,
23 utils::{address::RowAddress, deletion::DeletionVector},
24};
25use lance_io::ReadBatchParams;
26use tracing::instrument;
27
28use crate::rowids::{RowIdSequence, RowIdSequenceCursor};
29
30pub type ReadBatchFut = BoxFuture<'static, Result<RecordBatch>>;
31pub struct ReadBatchTask {
34 pub task: ReadBatchFut,
35 pub num_rows: u32,
36}
37pub type ReadBatchTaskStream = BoxStream<'static, ReadBatchTask>;
38pub type ReadBatchFutStream = BoxStream<'static, ReadBatchFut>;
39
40type SharedReadBatchFut = Shared<BoxFuture<'static, std::result::Result<RecordBatch, Arc<Error>>>>;
41
42#[derive(Debug)]
43struct SharedReadError(Arc<Error>);
44
45impl fmt::Display for SharedReadError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 self.0.fmt(f)
48 }
49}
50
51impl std::error::Error for SharedReadError {
52 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53 Some(self.0.as_ref())
54 }
55}
56
57struct PendingReadBatch {
58 task: Option<ReadBatchFut>,
59 shared_task: Option<SharedReadBatchFut>,
60 offset: u32,
61 num_rows: u32,
62}
63
64impl PendingReadBatch {
65 fn new(task: ReadBatchTask) -> Self {
66 Self {
67 task: Some(task.task),
68 shared_task: None,
69 offset: 0,
70 num_rows: task.num_rows,
71 }
72 }
73
74 fn take(&mut self, num_rows: u32) -> ReadBatchFut {
75 debug_assert!(num_rows <= self.num_rows);
76
77 if self.offset == 0 && num_rows == self.num_rows && self.shared_task.is_none() {
78 self.num_rows = 0;
79 let Some(task) = self.task.take() else {
80 return async {
81 Err(Error::internal(
82 "missing read task while merging aligned streams".to_string(),
83 ))
84 }
85 .boxed();
86 };
87 return task;
88 }
89
90 let shared_task = self
91 .shared_task
92 .get_or_insert_with(|| {
93 let task = self.task.take();
94 async move {
95 let Some(task) = task else {
96 return Err(Arc::new(Error::internal(
97 "missing read task while splitting a merged stream".to_string(),
98 )));
99 };
100 task.await.map_err(Arc::new)
101 }
102 .boxed()
103 .shared()
104 })
105 .clone();
106 let offset = self.offset;
107 self.offset += num_rows;
108 self.num_rows -= num_rows;
109
110 async move {
111 match shared_task.await {
112 Ok(batch) => Ok(batch.slice(offset as usize, num_rows as usize)),
113 Err(error) => Err(Error::wrapped(Box::new(SharedReadError(error)))),
114 }
115 }
116 .boxed()
117 }
118}
119
120struct MergeStream {
121 streams: Vec<ReadBatchTaskStream>,
122 pending: Vec<Option<PendingReadBatch>>,
123}
124
125impl MergeStream {
126 fn emit(&mut self) -> ReadBatchTask {
127 let num_rows = self
128 .pending
129 .iter()
130 .filter_map(|pending| pending.as_ref().map(|pending| pending.num_rows))
131 .min()
132 .unwrap_or_default();
133 let mut batches = FuturesOrdered::new();
134 for pending in &mut self.pending {
135 let Some(pending_batch) = pending.as_mut() else {
136 continue;
137 };
138 batches.push_back(pending_batch.take(num_rows));
139 if pending_batch.num_rows == 0 {
140 *pending = None;
141 }
142 }
143 let task = async move {
144 let Some(first) = batches.next().await else {
145 return Err(Error::internal(
146 "cannot merge an empty set of read batches".to_string(),
147 ));
148 };
149 let mut batch = first?;
150 while let Some(next) = batches.next().await {
151 let next = next?;
152 batch = batch.merge(&next)?;
153 }
154 Ok(batch)
155 }
156 .boxed();
157 ReadBatchTask { task, num_rows }
158 }
159}
160
161impl Stream for MergeStream {
162 type Item = ReadBatchTask;
163
164 fn poll_next(
165 mut self: std::pin::Pin<&mut Self>,
166 cx: &mut std::task::Context<'_>,
167 ) -> std::task::Poll<Option<Self::Item>> {
168 loop {
169 if self.pending.iter().all(Option::is_some) {
170 return std::task::Poll::Ready(Some(self.emit()));
171 }
172
173 let this = &mut *self;
189 let mut progressed = false;
190 for (index, stream) in this.streams.iter_mut().enumerate() {
191 if this.pending[index].is_some() {
192 continue;
193 }
194 match stream.poll_next_unpin(cx) {
195 std::task::Poll::Ready(Some(batch_task)) => {
196 this.pending[index] = Some(PendingReadBatch::new(batch_task));
197 progressed = true;
198 }
199 std::task::Poll::Ready(None) => {
200 return std::task::Poll::Ready(None);
201 }
202 std::task::Poll::Pending => {}
203 }
204 }
205 if !progressed {
206 return std::task::Poll::Pending;
207 }
208 }
209 }
210}
211
212pub fn merge_streams(streams: Vec<ReadBatchTaskStream>) -> ReadBatchTaskStream {
223 if streams.is_empty() {
224 return futures::stream::empty().boxed();
225 }
226 let pending = (0..streams.len()).map(|_| None).collect();
227 MergeStream { streams, pending }.boxed()
228}
229
230fn apply_deletions_as_nulls(batch: RecordBatch, mask: &BooleanArray) -> Result<RecordBatch> {
237 let mask_buffer = NullBuffer::new(mask.values().clone());
241
242 if mask_buffer.null_count() == 0 {
243 return Ok(batch);
245 }
246
247 let new_columns = batch
249 .schema()
250 .fields()
251 .iter()
252 .zip(batch.columns())
253 .map(|(field, col)| {
254 if field.name() == ROW_ID || field.name() == ROW_ADDR {
255 let col_data = col.to_data();
256 let null_buffer = NullBuffer::union(col_data.nulls(), Some(&mask_buffer));
259
260 Ok(col_data
261 .into_builder()
262 .null_bit_buffer(null_buffer.map(|b| b.buffer().clone()))
263 .build()
264 .map(make_array)?)
265 } else {
266 Ok(col.clone())
267 }
268 })
269 .collect::<Result<Vec<_>>>()?;
270
271 Ok(RecordBatch::try_new_with_options(
272 batch.schema(),
273 new_columns,
274 &RecordBatchOptions::new().with_row_count(Some(batch.num_rows())),
275 )?)
276}
277
278fn version_values_for_selection_with_cursor(
281 sequence: &crate::rowids::version::RowDatasetVersionSequence,
282 cursor: &mut crate::rowids::version::RowDatasetVersionCursor,
283 params: &ReadBatchParams,
284 batch_offset: u32,
285 num_rows: u32,
286) -> Result<Vec<u64>> {
287 let selection = params
288 .slice(batch_offset as usize, num_rows as usize)
289 .unwrap()
290 .to_ranges()
291 .unwrap();
292
293 if sequence.runs.len() == 1 {
294 return Ok(vec![sequence.runs[0].version(); num_rows as usize]);
295 }
296
297 let mut versions = Vec::with_capacity(num_rows as usize);
298 for r in &selection {
299 cursor.extend_range(sequence, r.start as usize..r.end as usize, &mut versions)?;
300 }
301 Ok(versions)
302}
303
304fn version_values_for_selection(
305 sequence: &crate::rowids::version::RowDatasetVersionSequence,
306 params: &ReadBatchParams,
307 batch_offset: u32,
308 num_rows: u32,
309) -> Result<Vec<u64>> {
310 let _selection = params
313 .slice(batch_offset as usize, num_rows as usize)
314 .unwrap()
315 .to_ranges()
316 .unwrap();
317 if sequence.runs.len() == 1 {
318 return Ok(vec![sequence.runs[0].version(); num_rows as usize]);
319 }
320 version_values_for_selection_with_cursor(
321 sequence,
322 &mut sequence.cursor(),
323 params,
324 batch_offset,
325 num_rows,
326 )
327}
328
329#[derive(Debug)]
331pub struct RowIdAndDeletesConfig {
332 pub params: ReadBatchParams,
334 pub with_row_id: bool,
336 pub with_row_addr: bool,
338 pub with_row_last_updated_at_version: bool,
340 pub with_row_created_at_version: bool,
342 pub deletion_vector: Option<Arc<DeletionVector>>,
344 pub row_id_sequence: Option<Arc<RowIdSequence>>,
346 pub last_updated_at_sequence: Option<Arc<crate::rowids::version::RowDatasetVersionSequence>>,
348 pub created_at_sequence: Option<Arc<crate::rowids::version::RowDatasetVersionSequence>>,
350 pub make_deletions_null: bool,
352 pub total_num_rows: u32,
356}
357
358impl RowIdAndDeletesConfig {
359 fn has_system_cols(&self) -> bool {
360 self.with_row_id
361 || self.with_row_addr
362 || self.with_row_last_updated_at_version
363 || self.with_row_created_at_version
364 }
365}
366
367pub fn apply_row_id_and_deletes(
368 batch: RecordBatch,
369 batch_offset: u32,
370 fragment_id: u32,
371 config: &RowIdAndDeletesConfig,
372) -> Result<RecordBatch> {
373 apply_row_id_and_deletes_with_system_columns(
374 batch,
375 batch_offset,
376 fragment_id,
377 config,
378 PrecomputedSystemColumns::default(),
379 None,
380 )
381}
382
383#[derive(Default)]
384struct PrecomputedSystemColumns {
385 row_ids: Option<Result<Arc<UInt64Array>>>,
386 last_updated_versions: Option<Result<Arc<UInt64Array>>>,
387 created_versions: Option<Result<Arc<UInt64Array>>>,
388}
389
390const ROW_ID_READ_AHEAD_ROWS: usize = 64 * 1024;
391
392struct PrecomputedRowIdChunk {
393 logical_offset: usize,
394 values: Arc<UInt64Array>,
395}
396
397struct CachedOutputSchema {
398 input: SchemaRef,
399 output: SchemaRef,
400}
401
402impl PrecomputedRowIdChunk {
403 fn end_offset(&self) -> usize {
404 self.logical_offset + self.values.len()
405 }
406
407 fn slice(&self, logical_offset: usize, num_rows: usize) -> Option<Arc<UInt64Array>> {
408 let offset_in_chunk = logical_offset.checked_sub(self.logical_offset)?;
409 if offset_in_chunk + num_rows > self.values.len() {
410 return None;
411 }
412 if offset_in_chunk == 0 && num_rows == self.values.len() {
413 return Some(self.values.clone());
414 }
415 Some(Arc::new(self.values.slice(offset_in_chunk, num_rows)))
416 }
417}
418
419fn decode_row_id_chunk<const USE_DENSE_ROW_ID_EXPANSION: bool>(
420 sequence: &RowIdSequence,
421 cursor: &mut RowIdSequenceCursor,
422 params: &ReadBatchParams,
423 logical_offset: usize,
424 chunk_len: usize,
425) -> Result<PrecomputedRowIdChunk> {
426 let selection = params
427 .slice(logical_offset, chunk_len)
428 .unwrap()
429 .to_ranges()
430 .unwrap();
431 let values = match selection.as_slice() {
432 [range] if USE_DENSE_ROW_ID_EXPANSION => UInt64Array::from(
433 sequence
434 .select_dense_range_with_cursor(cursor, range.start as usize..range.end as usize),
435 ),
436 [range] => UInt64Array::from(
437 sequence.select_range_with_cursor(cursor, range.start as usize..range.end as usize),
438 ),
439 _ => sequence
440 .select_with_cursor(
441 cursor,
442 selection
443 .iter()
444 .flat_map(|range| range.start as usize..range.end as usize),
445 )
446 .collect::<UInt64Array>(),
447 };
448 if values.len() != chunk_len {
449 return Err(Error::corrupt_file_named(
450 "row ID metadata",
451 format!(
452 "decoded row ID chunk at selected offset {logical_offset} contains {} rows, but the selection requires {chunk_len} rows",
453 values.len()
454 ),
455 ));
456 }
457 Ok(PrecomputedRowIdChunk {
458 logical_offset,
459 values: Arc::new(values),
460 })
461}
462
463fn selected_row_count(params: &ReadBatchParams, total_num_rows: usize) -> usize {
464 match params {
465 ReadBatchParams::Range(range) => range.len(),
466 ReadBatchParams::Ranges(ranges) => ranges
467 .iter()
468 .map(|range| (range.end - range.start) as usize)
469 .sum(),
470 ReadBatchParams::RangeFull => total_num_rows,
471 ReadBatchParams::RangeTo(range) => range.end,
472 ReadBatchParams::RangeFrom(range) => total_num_rows.saturating_sub(range.start),
473 ReadBatchParams::Indices(indices) => indices.len(),
474 }
475}
476
477#[instrument(name = "apply_row_id_and_deletes", level = "debug", skip_all)]
478fn apply_row_id_and_deletes_with_system_columns(
479 batch: RecordBatch,
480 batch_offset: u32,
481 fragment_id: u32,
482 config: &RowIdAndDeletesConfig,
483 precomputed: PrecomputedSystemColumns,
484 output_schema_cache: Option<&OnceLock<CachedOutputSchema>>,
485) -> Result<RecordBatch> {
486 let PrecomputedSystemColumns {
487 row_ids: precomputed_row_ids,
488 last_updated_versions,
489 created_versions,
490 } = precomputed;
491 let mut deletion_vector = config.deletion_vector.as_ref();
492 if let Some(deletion_vector_inner) = deletion_vector
494 && matches!(deletion_vector_inner.as_ref(), DeletionVector::NoDeletions)
495 {
496 deletion_vector = None;
497 }
498 let has_deletions = deletion_vector.is_some();
499 debug_assert!(batch.num_columns() > 0 || config.has_system_cols() || has_deletions);
500
501 let should_fetch_row_addr = config.with_row_addr
503 || (config.with_row_id && config.row_id_sequence.is_none())
504 || has_deletions;
505
506 let num_rows = batch.num_rows() as u32;
507
508 let row_addrs =
509 if should_fetch_row_addr {
510 let _rowaddrs = tracing::span!(tracing::Level::DEBUG, "fetch_row_addrs").entered();
511 let mut row_addrs = Vec::with_capacity(num_rows as usize);
512 for offset_range in config
513 .params
514 .slice(batch_offset as usize, num_rows as usize)
515 .unwrap()
516 .iter_offset_ranges()?
517 {
518 row_addrs.extend(offset_range.map(|row_offset| {
519 u64::from(RowAddress::new_from_parts(fragment_id, row_offset))
520 }));
521 }
522
523 Some(Arc::new(UInt64Array::from(row_addrs)))
524 } else {
525 None
526 };
527
528 let row_ids = if config.with_row_id {
529 let _rowids = tracing::span!(tracing::Level::DEBUG, "fetch_row_ids").entered();
530 if let Some(row_ids) = precomputed_row_ids {
531 let row_ids = row_ids?;
532 debug_assert_eq!(row_ids.len(), num_rows as usize);
533 Some(row_ids)
534 } else if let Some(row_id_sequence) = &config.row_id_sequence {
535 let selection = config
536 .params
537 .slice(batch_offset as usize, num_rows as usize)
538 .unwrap()
539 .to_ranges()
540 .unwrap();
541 let row_ids = row_id_sequence
542 .select(
543 selection
544 .iter()
545 .flat_map(|r| r.start as usize..r.end as usize),
546 )
547 .collect::<UInt64Array>();
548 Some(Arc::new(row_ids))
549 } else {
550 row_addrs.clone()
553 }
554 } else {
555 None
556 };
557
558 let span = tracing::span!(tracing::Level::DEBUG, "apply_deletions");
559 let _enter = span.enter();
560 let deletion_mask = deletion_vector.and_then(|v| {
561 let row_addrs: &[u64] = row_addrs.as_ref().unwrap().values();
562 v.build_predicate(row_addrs.iter())
563 });
564
565 let mut system_columns: Vec<(Field, ArrayRef)> = Vec::with_capacity(4);
566 if config.with_row_id {
567 system_columns.push((ROW_ID_FIELD.clone(), row_ids.unwrap()));
568 }
569 if config.with_row_addr {
570 system_columns.push((ROW_ADDR_FIELD.clone(), row_addrs.unwrap()));
571 }
572 if config.with_row_last_updated_at_version {
573 let version_arr = if let Some(version_arr) = last_updated_versions {
574 version_arr?
575 } else if let Some(sequence) = &config.last_updated_at_sequence {
576 Arc::new(UInt64Array::from(version_values_for_selection(
577 sequence,
578 &config.params,
579 batch_offset,
580 num_rows,
581 )?))
582 } else {
583 Arc::new(UInt64Array::from(vec![1u64; num_rows as usize]))
585 };
586 system_columns.push((ROW_LAST_UPDATED_AT_VERSION_FIELD.clone(), version_arr));
587 }
588 if config.with_row_created_at_version {
589 let version_arr = if let Some(version_arr) = created_versions {
590 version_arr?
591 } else if let Some(sequence) = &config.created_at_sequence {
592 Arc::new(UInt64Array::from(version_values_for_selection(
593 sequence,
594 &config.params,
595 batch_offset,
596 num_rows,
597 )?))
598 } else {
599 Arc::new(UInt64Array::from(vec![1u64; num_rows as usize]))
601 };
602 system_columns.push((ROW_CREATED_AT_VERSION_FIELD.clone(), version_arr));
603 }
604
605 let batch = if system_columns.is_empty() {
606 batch
607 } else if let Some(output_schema_cache) = output_schema_cache {
608 let input_schema = batch.schema();
609 let make_output_schema = || {
610 let mut fields = input_schema
611 .fields()
612 .iter()
613 .map(|field| field.as_ref().clone())
614 .collect::<Vec<_>>();
615 fields.extend(system_columns.iter().map(|(field, _)| field.clone()));
616 Arc::new(Schema::new_with_metadata(
617 fields,
618 input_schema.metadata().clone(),
619 ))
620 };
621 let cached = output_schema_cache.get_or_init(|| CachedOutputSchema {
622 input: input_schema.clone(),
623 output: make_output_schema(),
624 });
625 let output_schema = if Arc::ptr_eq(&cached.input, &input_schema)
626 || cached.input.as_ref() == input_schema.as_ref()
627 {
628 cached.output.clone()
629 } else {
630 make_output_schema()
631 };
632 let mut columns = Vec::with_capacity(batch.num_columns() + system_columns.len());
633 columns.extend_from_slice(batch.columns());
634 columns.extend(system_columns.into_iter().map(|(_, array)| array));
635 RecordBatch::try_new_with_options(
636 output_schema,
637 columns,
638 &RecordBatchOptions::new().with_row_count(Some(batch.num_rows())),
639 )?
640 } else {
641 system_columns
642 .into_iter()
643 .try_fold(batch, |batch, (field, array)| {
644 batch.try_with_column(field, array)
645 })?
646 };
647
648 match (deletion_mask, config.make_deletions_null) {
649 (None, _) => Ok(batch),
650 (Some(mask), false) => Ok(arrow::compute::filter_record_batch(&batch, &mask)?),
651 (Some(mask), true) => Ok(apply_deletions_as_nulls(batch, &mask)?),
652 }
653}
654
655pub fn wrap_with_row_id_and_delete(
661 stream: ReadBatchTaskStream,
662 fragment_id: u32,
663 config: RowIdAndDeletesConfig,
664) -> ReadBatchFutStream {
665 let (row_id_cursor, use_dense_row_id_expansion) = config
666 .row_id_sequence
667 .as_ref()
668 .filter(|_| config.with_row_id)
669 .map(|sequence| {
670 let (cursor, use_dense_range_expansion) = sequence.cursor_with_dense_range_expansion();
671 (Some(cursor), use_dense_range_expansion)
672 })
673 .unwrap_or((None, false));
674 if use_dense_row_id_expansion {
675 wrap_with_row_id_and_delete_impl::<true>(stream, fragment_id, config, row_id_cursor)
676 } else {
677 wrap_with_row_id_and_delete_impl::<false>(stream, fragment_id, config, row_id_cursor)
678 }
679}
680
681fn wrap_with_row_id_and_delete_impl<const USE_DENSE_ROW_ID_EXPANSION: bool>(
682 stream: ReadBatchTaskStream,
683 fragment_id: u32,
684 config: RowIdAndDeletesConfig,
685 mut row_id_cursor: Option<RowIdSequenceCursor>,
686) -> ReadBatchFutStream {
687 let config = Arc::new(config);
688 let output_schema_cache = Arc::new(OnceLock::new());
689 let mut row_id_chunk: Option<PrecomputedRowIdChunk> = None;
690 let mut uniform_batch_size = None;
691 let mut use_uniform_batch_fast_paths = true;
692 let selected_rows = selected_row_count(&config.params, config.total_num_rows as usize);
693 let mut last_updated_cursor = config
694 .last_updated_at_sequence
695 .as_ref()
696 .filter(|sequence| config.with_row_last_updated_at_version && sequence.runs.len() > 1)
697 .map(|sequence| sequence.cursor());
698 let mut created_cursor = config
699 .created_at_sequence
700 .as_ref()
701 .filter(|sequence| config.with_row_created_at_version && sequence.runs.len() > 1)
702 .map(|sequence| sequence.cursor());
703 let mut offset = 0;
704 stream
705 .map(move |batch_task| {
706 let config = config.clone();
707 let this_offset = offset;
708 let num_rows = batch_task.num_rows;
709 offset += num_rows;
710 let logical_offset = this_offset as usize;
711 let num_rows_usize = num_rows as usize;
712 if num_rows_usize != 0 && use_uniform_batch_fast_paths {
713 if let Some(batch_size) = uniform_batch_size {
714 if num_rows_usize != batch_size
719 && logical_offset + num_rows_usize < selected_rows
720 {
721 use_uniform_batch_fast_paths = false;
722 }
723 } else {
724 uniform_batch_size = Some(num_rows_usize);
725 }
726 }
727 let output_schema_cache = use_uniform_batch_fast_paths
728 .then(|| output_schema_cache.clone());
729 let row_ids = config.row_id_sequence.as_ref().and_then(|sequence| {
733 row_id_cursor.as_mut().map(|cursor| {
734 if num_rows_usize == 0 {
735 return Ok(Arc::new(UInt64Array::from(Vec::<u64>::new())));
736 }
737 if !use_uniform_batch_fast_paths
738 && row_id_chunk
739 .as_ref()
740 .is_none_or(|chunk| chunk.end_offset() <= logical_offset)
741 {
742 row_id_chunk = None;
743 return decode_row_id_chunk::<USE_DENSE_ROW_ID_EXPANSION>(
744 sequence,
745 cursor,
746 &config.params,
747 logical_offset,
748 num_rows_usize,
749 )
750 .map(|chunk| chunk.values);
751 }
752 if let Some(row_ids) = row_id_chunk
753 .as_ref()
754 .and_then(|chunk| chunk.slice(logical_offset, num_rows_usize))
755 {
756 return Ok(row_ids);
757 }
758
759 let prefix = row_id_chunk.as_ref().and_then(|chunk| {
760 let prefix_len = chunk.end_offset().checked_sub(logical_offset)?;
761 if prefix_len == 0 {
762 None
763 } else {
764 chunk.slice(logical_offset, prefix_len)
765 }
766 });
767 let decode_offset = logical_offset
768 + prefix
769 .as_ref()
770 .map(|row_ids| row_ids.len())
771 .unwrap_or_default();
772 let required_end = logical_offset + num_rows_usize;
773 let missing_rows = required_end.saturating_sub(decode_offset);
774 let chunk_len = if use_uniform_batch_fast_paths {
775 let batch_size = uniform_batch_size.unwrap_or(num_rows_usize);
776 let batches_per_chunk = (ROW_ID_READ_AHEAD_ROWS / batch_size).max(1);
777 let chunk_rows = batch_size.saturating_mul(batches_per_chunk);
778 missing_rows.max(chunk_rows)
779 } else {
780 missing_rows
781 }
782 .min(selected_rows.saturating_sub(decode_offset));
783 let chunk = decode_row_id_chunk::<USE_DENSE_ROW_ID_EXPANSION>(
784 sequence,
785 cursor,
786 &config.params,
787 decode_offset,
788 chunk_len,
789 )?;
790 let suffix = chunk.slice(decode_offset, missing_rows).ok_or_else(|| {
791 Error::corrupt_file_named(
792 "row ID metadata",
793 format!(
794 "decoded row ID chunk at selected offset {decode_offset} contains {} rows, but the current batch requires {missing_rows} more rows",
795 chunk.values.len()
796 ),
797 )
798 })?;
799 let row_ids = if let Some(prefix) = prefix {
800 let mut values = Vec::with_capacity(num_rows_usize);
801 values.extend_from_slice(prefix.values());
802 values.extend_from_slice(suffix.values());
803 Arc::new(UInt64Array::from(values))
804 } else {
805 suffix
806 };
807 row_id_chunk = Some(chunk);
808 Ok(row_ids)
809 })
810 });
811 let last_updated_versions =
812 config
813 .last_updated_at_sequence
814 .as_ref()
815 .and_then(|sequence| {
816 last_updated_cursor.as_mut().map(|cursor| {
817 version_values_for_selection_with_cursor(
818 sequence,
819 cursor,
820 &config.params,
821 this_offset,
822 num_rows,
823 )
824 .map(UInt64Array::from)
825 .map(Arc::new)
826 })
827 });
828 let created_versions = config.created_at_sequence.as_ref().and_then(|sequence| {
829 created_cursor.as_mut().map(|cursor| {
830 version_values_for_selection_with_cursor(
831 sequence,
832 cursor,
833 &config.params,
834 this_offset,
835 num_rows,
836 )
837 .map(UInt64Array::from)
838 .map(Arc::new)
839 })
840 });
841 batch_task
842 .task
843 .map(move |batch| {
844 apply_row_id_and_deletes_with_system_columns(
845 batch?,
846 this_offset,
847 fragment_id,
848 config.as_ref(),
849 PrecomputedSystemColumns {
850 row_ids,
851 last_updated_versions,
852 created_versions,
853 },
854 output_schema_cache.as_deref(),
855 )
856 })
857 .boxed()
858 })
859 .boxed()
860}
861
862#[cfg(test)]
863mod tests {
864 use std::sync::Arc;
865
866 use arrow::{array::AsArray, datatypes::UInt64Type};
867 use arrow_array::{RecordBatch, UInt32Array, types::Int32Type};
868 use arrow_schema::ArrowError;
869 use futures::{
870 FutureExt, StreamExt, TryStreamExt,
871 stream::{self, BoxStream},
872 };
873 use lance_core::{
874 ROW_ID,
875 utils::{address::RowAddress, deletion::DeletionVector},
876 };
877 use lance_datagen::{BatchCount, RowCount};
878 use lance_io::{ReadBatchParams, stream::arrow_stream_to_lance_stream};
879 use roaring::RoaringBitmap;
880
881 use crate::{rowids::RowIdSequence, utils::stream::ReadBatchTask};
882
883 use super::RowIdAndDeletesConfig;
884
885 fn batch_task_stream(
886 datagen_stream: BoxStream<'static, std::result::Result<RecordBatch, ArrowError>>,
887 ) -> super::ReadBatchTaskStream {
888 arrow_stream_to_lance_stream(datagen_stream)
889 .map(|batch| ReadBatchTask {
890 num_rows: batch.as_ref().unwrap().num_rows() as u32,
891 task: std::future::ready(batch).boxed(),
892 })
893 .boxed()
894 }
895
896 #[tokio::test]
897 async fn test_basic_zip() {
898 let left = batch_task_stream(
899 lance_datagen::gen_batch()
900 .col("x", lance_datagen::array::step::<Int32Type>())
901 .into_reader_stream(RowCount::from(100), BatchCount::from(10))
902 .0,
903 );
904 let right = batch_task_stream(
905 lance_datagen::gen_batch()
906 .col("y", lance_datagen::array::step::<Int32Type>())
907 .into_reader_stream(RowCount::from(100), BatchCount::from(10))
908 .0,
909 );
910
911 let merged = super::merge_streams(vec![left, right])
912 .map(|batch_task| batch_task.task)
913 .buffered(1)
914 .try_collect::<Vec<_>>()
915 .await
916 .unwrap();
917
918 let expected = lance_datagen::gen_batch()
919 .col("x", lance_datagen::array::step::<Int32Type>())
920 .col("y", lance_datagen::array::step::<Int32Type>())
921 .into_reader_rows(RowCount::from(100), BatchCount::from(10))
922 .collect::<Result<Vec<_>, ArrowError>>()
923 .unwrap();
924 assert_eq!(merged, expected);
925 }
926
927 #[tokio::test]
928 async fn test_stable_row_ids_across_concurrent_batches_and_deletes() {
929 let expected = (10_000..120_000)
930 .filter(|row_id| row_id % 13 != 0)
931 .collect::<Vec<u64>>();
932 let row_id_sequence = Arc::new(RowIdSequence::try_from_iter(expected.clone()).unwrap());
933 let deletion_offsets = (0..expected.len() as u32).step_by(997).collect::<Vec<_>>();
934 let deletion_vector = Some(Arc::new(DeletionVector::Bitmap(
935 deletion_offsets.iter().copied().collect(),
936 )));
937
938 let batches = expected
939 .chunks(257)
940 .map(|chunk| arrow_array::record_batch!(("x", Int32, vec![0; chunk.len()])).unwrap())
941 .map(Ok)
942 .collect::<Vec<std::result::Result<RecordBatch, ArrowError>>>();
943 let data = batch_task_stream(stream::iter(batches).boxed());
944 let config = RowIdAndDeletesConfig {
945 params: ReadBatchParams::RangeFull,
946 with_row_id: true,
947 with_row_addr: true,
948 with_row_last_updated_at_version: false,
949 with_row_created_at_version: false,
950 deletion_vector,
951 row_id_sequence: Some(row_id_sequence),
952 last_updated_at_sequence: None,
953 created_at_sequence: None,
954 make_deletions_null: false,
955 total_num_rows: expected.len() as u32,
956 };
957
958 let batches = super::wrap_with_row_id_and_delete(data, 7, config)
959 .buffered(8)
960 .try_collect::<Vec<_>>()
961 .await
962 .unwrap();
963 let actual_row_ids = batches
964 .iter()
965 .flat_map(|batch| batch[ROW_ID].as_primitive::<UInt64Type>().values())
966 .copied()
967 .collect::<Vec<_>>();
968 let actual_row_addrs = batches
969 .iter()
970 .flat_map(|batch| {
971 batch[lance_core::ROW_ADDR]
972 .as_primitive::<UInt64Type>()
973 .values()
974 })
975 .copied()
976 .collect::<Vec<_>>();
977 let expected_survivors = expected
978 .iter()
979 .enumerate()
980 .filter(|(offset, _)| deletion_offsets.binary_search(&(*offset as u32)).is_err())
981 .map(|(offset, row_id)| {
982 (
983 *row_id,
984 u64::from(RowAddress::new_from_parts(7, offset as u32)),
985 )
986 })
987 .collect::<Vec<_>>();
988
989 assert_eq!(
990 actual_row_ids,
991 expected_survivors
992 .iter()
993 .map(|(row_id, _)| *row_id)
994 .collect::<Vec<_>>()
995 );
996 assert_eq!(
997 actual_row_addrs,
998 expected_survivors
999 .iter()
1000 .map(|(_, row_addr)| *row_addr)
1001 .collect::<Vec<_>>()
1002 );
1003 }
1004
1005 #[tokio::test]
1006 async fn test_stable_row_ids_with_unsorted_indices() {
1007 let expected = (100..140)
1008 .filter(|row_id| row_id % 3 != 0)
1009 .collect::<Vec<u64>>();
1010 let indices = UInt32Array::from(vec![8, 2, 9, 1, 6]);
1011 let batches = [2, 2, 1].into_iter().map(|num_rows| ReadBatchTask {
1012 num_rows,
1013 task: std::future::ready(Ok(arrow_array::record_batch!((
1014 "x",
1015 Int32,
1016 vec![0; num_rows as usize]
1017 ))
1018 .unwrap()))
1019 .boxed(),
1020 });
1021 let config = RowIdAndDeletesConfig {
1022 params: ReadBatchParams::Indices(indices.clone()),
1023 with_row_id: true,
1024 with_row_addr: false,
1025 with_row_last_updated_at_version: false,
1026 with_row_created_at_version: false,
1027 deletion_vector: None,
1028 row_id_sequence: Some(Arc::new(
1029 RowIdSequence::try_from_iter(expected.clone()).unwrap(),
1030 )),
1031 last_updated_at_sequence: None,
1032 created_at_sequence: None,
1033 make_deletions_null: false,
1034 total_num_rows: expected.len() as u32,
1035 };
1036
1037 let actual = super::wrap_with_row_id_and_delete(stream::iter(batches).boxed(), 7, config)
1038 .buffered(3)
1039 .try_collect::<Vec<_>>()
1040 .await
1041 .unwrap()
1042 .iter()
1043 .flat_map(|batch| batch[ROW_ID].as_primitive::<UInt64Type>().values())
1044 .copied()
1045 .collect::<Vec<_>>();
1046 let expected = indices
1047 .values()
1048 .iter()
1049 .map(|index| expected[*index as usize])
1050 .collect::<Vec<_>>();
1051 assert_eq!(actual, expected);
1052 }
1053
1054 #[tokio::test]
1055 async fn test_repeated_row_id_after_bulk_segment_boundary() {
1056 let mut row_ids = RowIdSequence::from(0..5);
1057 row_ids.extend(RowIdSequence::from(10..20));
1058 let batches = [1_u32, 2].into_iter().map(|num_rows| ReadBatchTask {
1059 num_rows,
1060 task: std::future::ready(Ok(arrow_array::record_batch!((
1061 "x",
1062 Int32,
1063 vec![0; num_rows as usize]
1064 ))
1065 .unwrap()))
1066 .boxed(),
1067 });
1068 let config = RowIdAndDeletesConfig {
1069 params: ReadBatchParams::Indices(UInt32Array::from(vec![4, 4, 5])),
1070 with_row_id: true,
1071 with_row_addr: false,
1072 with_row_last_updated_at_version: false,
1073 with_row_created_at_version: false,
1074 deletion_vector: None,
1075 row_id_sequence: Some(Arc::new(row_ids)),
1076 last_updated_at_sequence: None,
1077 created_at_sequence: None,
1078 make_deletions_null: false,
1079 total_num_rows: 15,
1080 };
1081
1082 let actual = super::wrap_with_row_id_and_delete(stream::iter(batches).boxed(), 0, config)
1083 .buffered(1)
1084 .try_collect::<Vec<_>>()
1085 .await
1086 .unwrap()
1087 .iter()
1088 .flat_map(|batch| batch[ROW_ID].as_primitive::<UInt64Type>().values())
1089 .copied()
1090 .collect::<Vec<_>>();
1091 assert_eq!(actual, vec![4, 4, 10]);
1092 }
1093
1094 #[tokio::test]
1095 async fn test_stable_row_id_read_ahead_range_boundary_and_tail() {
1096 let all_row_ids = (10_000..120_000)
1097 .filter(|row_id| row_id % 11 != 0)
1098 .collect::<Vec<u64>>();
1099 let selection = 1_234..71_237;
1100 let selected_len = selection.len();
1101 let mut remaining = selected_len;
1102 let tasks = std::iter::from_fn(move || {
1103 if remaining == 0 {
1104 return None;
1105 }
1106 let num_rows = remaining.min(1_025);
1107 remaining -= num_rows;
1108 Some(ReadBatchTask {
1109 num_rows: num_rows as u32,
1110 task: std::future::ready(Ok(arrow_array::record_batch!((
1111 "x",
1112 Int32,
1113 vec![0; num_rows]
1114 ))
1115 .unwrap()))
1116 .boxed(),
1117 })
1118 });
1119 let config = RowIdAndDeletesConfig {
1120 params: ReadBatchParams::Range(selection.clone()),
1121 with_row_id: true,
1122 with_row_addr: false,
1123 with_row_last_updated_at_version: false,
1124 with_row_created_at_version: false,
1125 deletion_vector: None,
1126 row_id_sequence: Some(Arc::new(
1127 RowIdSequence::try_from_iter(all_row_ids.clone()).unwrap(),
1128 )),
1129 last_updated_at_sequence: None,
1130 created_at_sequence: None,
1131 make_deletions_null: false,
1132 total_num_rows: all_row_ids.len() as u32,
1133 };
1134
1135 let batches = super::wrap_with_row_id_and_delete(stream::iter(tasks).boxed(), 3, config)
1136 .buffered(8)
1137 .try_collect::<Vec<_>>()
1138 .await
1139 .unwrap();
1140 assert_eq!(batches.len(), 69);
1141 assert_eq!(batches.last().unwrap().num_rows(), 303);
1142
1143 fn row_ids(batch: &RecordBatch) -> &arrow_array::UInt64Array {
1144 batch[ROW_ID].as_primitive::<UInt64Type>()
1145 }
1146 assert_eq!(
1147 row_ids(&batches[62]).values().as_ptr(),
1148 row_ids(&batches[0])
1149 .values()
1150 .as_ptr()
1151 .wrapping_add(62 * 1_025)
1152 );
1153 assert_eq!(
1154 row_ids(&batches[68]).values().as_ptr(),
1155 row_ids(&batches[64])
1156 .values()
1157 .as_ptr()
1158 .wrapping_add(4 * 1_025)
1159 );
1160
1161 let actual = batches
1162 .iter()
1163 .flat_map(|batch| row_ids(batch).values())
1164 .copied()
1165 .collect::<Vec<_>>();
1166 assert_eq!(actual, all_row_ids[selection]);
1167 }
1168
1169 #[tokio::test]
1170 async fn test_stable_row_id_read_ahead_empty_task() {
1171 let tasks = [0_u32, 1].into_iter().map(|num_rows| ReadBatchTask {
1172 num_rows,
1173 task: std::future::ready(Ok(arrow_array::record_batch!((
1174 "x",
1175 Int32,
1176 vec![0; num_rows as usize]
1177 ))
1178 .unwrap()))
1179 .boxed(),
1180 });
1181 let config = RowIdAndDeletesConfig {
1182 params: ReadBatchParams::RangeFull,
1183 with_row_id: true,
1184 with_row_addr: false,
1185 with_row_last_updated_at_version: false,
1186 with_row_created_at_version: false,
1187 deletion_vector: None,
1188 row_id_sequence: Some(Arc::new(RowIdSequence::try_from_iter([42]).unwrap())),
1189 last_updated_at_sequence: None,
1190 created_at_sequence: None,
1191 make_deletions_null: false,
1192 total_num_rows: 1,
1193 };
1194
1195 let batches = super::wrap_with_row_id_and_delete(stream::iter(tasks).boxed(), 0, config)
1196 .buffered(2)
1197 .try_collect::<Vec<_>>()
1198 .await
1199 .unwrap();
1200 assert_eq!(batches[0].num_rows(), 0);
1201 assert_eq!(
1202 batches[1][ROW_ID].as_primitive::<UInt64Type>().values(),
1203 &[42]
1204 );
1205 }
1206
1207 #[tokio::test]
1208 async fn test_truncated_stable_row_ids_returns_error() {
1209 let task = ReadBatchTask {
1210 num_rows: 10,
1211 task: std::future::ready(Ok(
1212 arrow_array::record_batch!(("x", Int32, vec![0; 10])).unwrap()
1213 ))
1214 .boxed(),
1215 };
1216 let config = RowIdAndDeletesConfig {
1217 params: ReadBatchParams::RangeFull,
1218 with_row_id: true,
1219 with_row_addr: false,
1220 with_row_last_updated_at_version: false,
1221 with_row_created_at_version: false,
1222 deletion_vector: None,
1223 row_id_sequence: Some(Arc::new(RowIdSequence::try_from_iter(0_u64..5).unwrap())),
1224 last_updated_at_sequence: None,
1225 created_at_sequence: None,
1226 make_deletions_null: false,
1227 total_num_rows: 10,
1228 };
1229
1230 let error = super::wrap_with_row_id_and_delete(stream::iter([task]).boxed(), 0, config)
1231 .buffered(1)
1232 .try_collect::<Vec<_>>()
1233 .await
1234 .unwrap_err();
1235 assert!(matches!(error, lance_core::Error::CorruptFile { .. }));
1236 assert!(error.to_string().contains(
1237 "decoded row ID chunk at selected offset 0 contains 5 rows, but the selection requires 10 rows"
1238 ));
1239 }
1240
1241 #[tokio::test]
1242 async fn test_truncated_stable_row_ids_with_unsorted_indices_returns_error() {
1243 let tasks = (0..4).map(|_| ReadBatchTask {
1244 num_rows: 1,
1245 task: std::future::ready(Ok(
1246 arrow_array::record_batch!(("x", Int32, vec![0])).unwrap()
1247 ))
1248 .boxed(),
1249 });
1250 let config = RowIdAndDeletesConfig {
1251 params: ReadBatchParams::Indices(UInt32Array::from(vec![0, 5, 1, 2])),
1252 with_row_id: true,
1253 with_row_addr: false,
1254 with_row_last_updated_at_version: false,
1255 with_row_created_at_version: false,
1256 deletion_vector: None,
1257 row_id_sequence: Some(Arc::new(RowIdSequence::try_from_iter(0_u64..5).unwrap())),
1258 last_updated_at_sequence: None,
1259 created_at_sequence: None,
1260 make_deletions_null: false,
1261 total_num_rows: 6,
1262 };
1263
1264 let error = super::wrap_with_row_id_and_delete(stream::iter(tasks).boxed(), 0, config)
1265 .buffered(1)
1266 .try_collect::<Vec<_>>()
1267 .await
1268 .unwrap_err();
1269 assert!(matches!(error, lance_core::Error::CorruptFile { .. }));
1270 }
1271
1272 #[tokio::test]
1273 async fn test_stable_row_id_read_ahead_with_variable_task_boundaries() {
1274 let total_rows = super::ROW_ID_READ_AHEAD_ROWS * 3 + 41;
1275 let expected = (0_u64..)
1276 .filter(|row_id| row_id % 17 != 0)
1277 .take(total_rows)
1278 .collect::<Vec<_>>();
1279 let mut remaining = total_rows;
1280 let mut use_short_task = true;
1281 let tasks = std::iter::from_fn(move || {
1282 if remaining == 0 {
1283 return None;
1284 }
1285 let requested = if use_short_task { 32_768 } else { 32_769 };
1286 use_short_task = !use_short_task;
1287 let num_rows = remaining.min(requested);
1288 remaining -= num_rows;
1289 Some(ReadBatchTask {
1290 num_rows: num_rows as u32,
1291 task: std::future::ready(Ok(arrow_array::record_batch!((
1292 "x",
1293 Int32,
1294 vec![0; num_rows]
1295 ))
1296 .unwrap()))
1297 .boxed(),
1298 })
1299 });
1300 let config = RowIdAndDeletesConfig {
1301 params: ReadBatchParams::RangeFull,
1302 with_row_id: true,
1303 with_row_addr: false,
1304 with_row_last_updated_at_version: false,
1305 with_row_created_at_version: false,
1306 deletion_vector: None,
1307 row_id_sequence: Some(Arc::new(
1308 RowIdSequence::try_from_iter(expected.clone()).unwrap(),
1309 )),
1310 last_updated_at_sequence: None,
1311 created_at_sequence: None,
1312 make_deletions_null: false,
1313 total_num_rows: total_rows as u32,
1314 };
1315
1316 let batches = super::wrap_with_row_id_and_delete(stream::iter(tasks).boxed(), 0, config)
1317 .buffered(3)
1318 .try_collect::<Vec<_>>()
1319 .await
1320 .unwrap();
1321 let second_schema = batches[1].schema();
1322 let third_schema = batches[2].schema();
1323 assert!(!Arc::ptr_eq(&second_schema, &third_schema));
1324
1325 let actual = batches
1326 .iter()
1327 .flat_map(|batch| batch[ROW_ID].as_primitive::<UInt64Type>().values())
1328 .copied()
1329 .collect::<Vec<_>>();
1330 assert_eq!(actual, expected);
1331 }
1332
1333 #[tokio::test]
1334 async fn test_system_columns_share_schema_for_equivalent_payload_batches() {
1335 let batches = (0..3)
1336 .map(|batch_index| {
1337 arrow_array::record_batch!((
1338 "payload",
1339 Int32,
1340 (batch_index * 10..(batch_index + 1) * 10).collect::<Vec<_>>()
1341 ))
1342 .unwrap()
1343 })
1344 .collect::<Vec<_>>();
1345 assert!(!Arc::ptr_eq(&batches[0].schema(), &batches[1].schema()));
1346 let tasks = batches.into_iter().map(|batch| ReadBatchTask {
1347 num_rows: batch.num_rows() as u32,
1348 task: std::future::ready(Ok(batch)).boxed(),
1349 });
1350 let config = RowIdAndDeletesConfig {
1351 params: ReadBatchParams::RangeFull,
1352 with_row_id: true,
1353 with_row_addr: true,
1354 with_row_last_updated_at_version: true,
1355 with_row_created_at_version: true,
1356 deletion_vector: None,
1357 row_id_sequence: Some(Arc::new(
1358 RowIdSequence::try_from_iter((0..30).map(|row_id| 100 + row_id + row_id / 7))
1359 .unwrap(),
1360 )),
1361 last_updated_at_sequence: None,
1362 created_at_sequence: None,
1363 make_deletions_null: false,
1364 total_num_rows: 30,
1365 };
1366
1367 let batches = super::wrap_with_row_id_and_delete(stream::iter(tasks).boxed(), 7, config)
1368 .buffered(3)
1369 .try_collect::<Vec<_>>()
1370 .await
1371 .unwrap();
1372 let expected_fields = [
1373 "payload",
1374 lance_core::ROW_ID,
1375 lance_core::ROW_ADDR,
1376 lance_core::ROW_LAST_UPDATED_AT_VERSION,
1377 lance_core::ROW_CREATED_AT_VERSION,
1378 ];
1379 assert_eq!(
1380 batches[0]
1381 .schema()
1382 .fields()
1383 .iter()
1384 .map(|field| field.name().as_str())
1385 .collect::<Vec<_>>(),
1386 expected_fields
1387 );
1388 assert!(
1389 batches
1390 .windows(2)
1391 .all(|pair| Arc::ptr_eq(&pair[0].schema(), &pair[1].schema()))
1392 );
1393 assert!(batches.iter().all(|batch| batch.num_columns() == 5));
1394 }
1395
1396 #[tokio::test]
1397 async fn test_zip_with_different_batch_boundaries() {
1398 let left_batch =
1399 arrow_array::record_batch!(("x", Int32, (0..10).collect::<Vec<_>>())).unwrap();
1400 let right_batch =
1401 arrow_array::record_batch!(("y", Int32, (10..20).collect::<Vec<_>>())).unwrap();
1402 let left = batch_task_stream(
1403 stream::iter([Ok(left_batch.slice(0, 6)), Ok(left_batch.slice(6, 4))]).boxed(),
1404 );
1405 let right = batch_task_stream(
1406 stream::iter([Ok(right_batch.slice(0, 4)), Ok(right_batch.slice(4, 6))]).boxed(),
1407 );
1408
1409 let merged = super::merge_streams(vec![left, right])
1410 .map(|batch_task| batch_task.task)
1411 .buffered(3)
1412 .try_collect::<Vec<_>>()
1413 .await
1414 .unwrap();
1415
1416 let expected = vec![
1417 arrow_array::record_batch!(
1418 ("x", Int32, (0..4).collect::<Vec<_>>()),
1419 ("y", Int32, (10..14).collect::<Vec<_>>())
1420 )
1421 .unwrap(),
1422 arrow_array::record_batch!(
1423 ("x", Int32, (4..6).collect::<Vec<_>>()),
1424 ("y", Int32, (14..16).collect::<Vec<_>>())
1425 )
1426 .unwrap(),
1427 arrow_array::record_batch!(
1428 ("x", Int32, (6..10).collect::<Vec<_>>()),
1429 ("y", Int32, (16..20).collect::<Vec<_>>())
1430 )
1431 .unwrap(),
1432 ];
1433 assert_eq!(merged, expected);
1434 }
1435
1436 async fn check_row_id(params: ReadBatchParams, expected: impl IntoIterator<Item = u32>) {
1437 let expected = Vec::from_iter(expected);
1438
1439 for has_columns in [false, true] {
1440 for fragment_id in [0, 10] {
1441 let mut datagen = lance_datagen::gen_batch();
1443 if has_columns {
1444 datagen = datagen.col("x", lance_datagen::array::rand::<Int32Type>());
1445 }
1446 let data = batch_task_stream(
1447 datagen
1448 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
1449 .0,
1450 );
1451
1452 let config = RowIdAndDeletesConfig {
1453 params: params.clone(),
1454 with_row_id: true,
1455 with_row_addr: false,
1456 with_row_last_updated_at_version: false,
1457 with_row_created_at_version: false,
1458 deletion_vector: None,
1459 row_id_sequence: None,
1460 last_updated_at_sequence: None,
1461 created_at_sequence: None,
1462 make_deletions_null: false,
1463 total_num_rows: 100,
1464 };
1465 let stream = super::wrap_with_row_id_and_delete(data, fragment_id, config);
1466 let batches = stream.buffered(1).try_collect::<Vec<_>>().await.unwrap();
1467
1468 let mut offset = 0;
1469 let expected = expected.clone();
1470 for batch in batches {
1471 let actual_row_ids =
1472 batch[ROW_ID].as_primitive::<UInt64Type>().values().to_vec();
1473 let expected_row_ids = expected[offset..offset + 10]
1474 .iter()
1475 .map(|row_offset| {
1476 RowAddress::new_from_parts(fragment_id, *row_offset).into()
1477 })
1478 .collect::<Vec<u64>>();
1479 assert_eq!(actual_row_ids, expected_row_ids);
1480 offset += batch.num_rows();
1481 }
1482 }
1483 }
1484 }
1485
1486 #[tokio::test]
1487 async fn test_row_id() {
1488 let some_indices = (0..100).rev().collect::<Vec<u32>>();
1489 let some_indices_arr = UInt32Array::from(some_indices.clone());
1490 check_row_id(ReadBatchParams::RangeFull, 0..100).await;
1491 check_row_id(ReadBatchParams::Indices(some_indices_arr), some_indices).await;
1492 check_row_id(ReadBatchParams::Range(1000..1100), 1000..1100).await;
1493 check_row_id(
1494 ReadBatchParams::RangeFrom(std::ops::RangeFrom { start: 1000 }),
1495 1000..1100,
1496 )
1497 .await;
1498 check_row_id(
1499 ReadBatchParams::RangeTo(std::ops::RangeTo { end: 1000 }),
1500 0..100,
1501 )
1502 .await;
1503 }
1504
1505 #[tokio::test]
1506 async fn test_deletes() {
1507 let no_deletes: Option<Arc<DeletionVector>> = None;
1508 let no_deletes_2 = Some(Arc::new(DeletionVector::NoDeletions));
1509 let delete_some_bitmap = Some(Arc::new(DeletionVector::Bitmap(RoaringBitmap::from_iter(
1510 0..35,
1511 ))));
1512 let delete_some_set = Some(Arc::new(DeletionVector::Set((0..35).collect())));
1513
1514 for deletion_vector in [
1515 no_deletes,
1516 no_deletes_2,
1517 delete_some_bitmap,
1518 delete_some_set,
1519 ] {
1520 for has_columns in [false, true] {
1521 for with_row_id in [false, true] {
1522 for make_deletions_null in [false, true] {
1523 for frag_id in [0, 1] {
1524 let has_deletions = if let Some(dv) = &deletion_vector {
1525 !matches!(dv.as_ref(), DeletionVector::NoDeletions)
1526 } else {
1527 false
1528 };
1529 if !has_columns && !has_deletions && !with_row_id {
1530 continue;
1533 }
1534 if make_deletions_null && !with_row_id {
1535 continue;
1538 }
1539
1540 let mut datagen = lance_datagen::gen_batch();
1541 if has_columns {
1542 datagen =
1543 datagen.col("x", lance_datagen::array::rand::<Int32Type>());
1544 }
1545 let data = batch_task_stream(
1547 datagen
1548 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
1549 .0,
1550 );
1551
1552 let config = RowIdAndDeletesConfig {
1553 params: ReadBatchParams::RangeFull,
1554 with_row_id,
1555 with_row_addr: false,
1556 with_row_last_updated_at_version: false,
1557 with_row_created_at_version: false,
1558 deletion_vector: deletion_vector.clone(),
1559 row_id_sequence: None,
1560 last_updated_at_sequence: None,
1561 created_at_sequence: None,
1562 make_deletions_null,
1563 total_num_rows: 100,
1564 };
1565 let stream = super::wrap_with_row_id_and_delete(data, frag_id, config);
1566 let batches = stream
1567 .buffered(1)
1568 .filter_map(|batch| {
1569 std::future::ready(
1570 batch
1571 .map(|batch| {
1572 if batch.num_rows() == 0 {
1573 None
1574 } else {
1575 Some(batch)
1576 }
1577 })
1578 .transpose(),
1579 )
1580 })
1581 .try_collect::<Vec<_>>()
1582 .await
1583 .unwrap();
1584
1585 let total_num_rows =
1586 batches.iter().map(|b| b.num_rows()).sum::<usize>();
1587 let total_num_nulls = if make_deletions_null {
1588 batches
1589 .iter()
1590 .map(|b| b[ROW_ID].null_count())
1591 .sum::<usize>()
1592 } else {
1593 0
1594 };
1595 let total_actually_deleted = total_num_nulls + (100 - total_num_rows);
1596
1597 let expected_deletions = match &deletion_vector {
1598 None => 0,
1599 Some(deletion_vector) => match deletion_vector.as_ref() {
1600 DeletionVector::NoDeletions => 0,
1601 DeletionVector::Bitmap(b) => b.len() as usize,
1602 DeletionVector::Set(s) => s.len(),
1603 },
1604 };
1605 assert_eq!(total_actually_deleted, expected_deletions);
1606 if expected_deletions > 0 && with_row_id {
1607 if make_deletions_null {
1608 assert_eq!(
1611 batches[3][ROW_ID].as_primitive::<UInt64Type>().value(0),
1612 u64::from(RowAddress::new_from_parts(frag_id, 30))
1613 );
1614 assert_eq!(batches[3][ROW_ID].null_count(), 5);
1615 } else {
1616 assert_eq!(
1618 batches[0][ROW_ID].as_primitive::<UInt64Type>().value(0),
1619 u64::from(RowAddress::new_from_parts(frag_id, 35))
1620 );
1621 }
1622 }
1623 if !with_row_id {
1624 assert!(batches[0].column_by_name(ROW_ID).is_none());
1625 }
1626 }
1627 }
1628 }
1629 }
1630 }
1631 }
1632
1633 #[tokio::test]
1634 async fn test_version_column_with_deletions() {
1635 use crate::rowids::segment::U64Segment;
1636 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
1637
1638 let seq = Arc::new(RowDatasetVersionSequence {
1639 runs: vec![RowDatasetVersionRun {
1640 span: U64Segment::Range(0..100),
1641 version: 42,
1642 }],
1643 });
1644
1645 let data = batch_task_stream(
1646 lance_datagen::gen_batch()
1647 .col("x", lance_datagen::array::rand::<Int32Type>())
1648 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
1649 .0,
1650 );
1651
1652 let config = RowIdAndDeletesConfig {
1653 params: ReadBatchParams::RangeFull,
1654 with_row_id: true,
1655 with_row_addr: false,
1656 with_row_last_updated_at_version: false,
1657 with_row_created_at_version: true,
1658 deletion_vector: Some(Arc::new(DeletionVector::Bitmap(RoaringBitmap::from_iter(
1659 0..35,
1660 )))),
1661 row_id_sequence: None,
1662 last_updated_at_sequence: None,
1663 created_at_sequence: Some(seq),
1664 make_deletions_null: false,
1665 total_num_rows: 100,
1666 };
1667 let stream = super::wrap_with_row_id_and_delete(data, 0, config);
1668 let batches: Vec<_> = stream
1669 .buffered(1)
1670 .try_filter(|b| std::future::ready(b.num_rows() > 0))
1671 .try_collect()
1672 .await
1673 .unwrap();
1674
1675 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
1676 assert_eq!(total_rows, 65);
1677
1678 for batch in &batches {
1679 let versions = batch
1680 .column_by_name("_row_created_at_version")
1681 .unwrap()
1682 .as_primitive::<UInt64Type>()
1683 .values();
1684 assert!(versions.iter().all(|&v| v == 42));
1685 }
1686 }
1687
1688 #[tokio::test]
1689 async fn test_version_column_multi_run() {
1690 use crate::rowids::segment::U64Segment;
1691 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
1692
1693 let created_seq = Arc::new(RowDatasetVersionSequence {
1695 runs: (0..100)
1696 .map(|position| RowDatasetVersionRun {
1697 span: U64Segment::Range(position..position + 1),
1698 version: 1_000 + position,
1699 })
1700 .collect(),
1701 });
1702 let last_updated_seq = Arc::new(RowDatasetVersionSequence {
1704 runs: vec![
1705 RowDatasetVersionRun {
1706 span: U64Segment::Range(0..7),
1707 version: 11,
1708 },
1709 RowDatasetVersionRun {
1710 span: U64Segment::Range(7..20),
1711 version: 22,
1712 },
1713 RowDatasetVersionRun {
1714 span: U64Segment::Range(20..21),
1715 version: 33,
1716 },
1717 RowDatasetVersionRun {
1718 span: U64Segment::Range(21..50),
1719 version: 44,
1720 },
1721 RowDatasetVersionRun {
1722 span: U64Segment::Range(50..100),
1723 version: 55,
1724 },
1725 ],
1726 });
1727
1728 let mut deletions = RoaringBitmap::from_iter(0..20);
1731 deletions.extend(60..80);
1732
1733 let data = batch_task_stream(
1734 lance_datagen::gen_batch()
1735 .col("x", lance_datagen::array::rand::<Int32Type>())
1736 .into_reader_stream(RowCount::from(10), BatchCount::from(10))
1737 .0,
1738 );
1739
1740 let config = RowIdAndDeletesConfig {
1741 params: ReadBatchParams::RangeFull,
1742 with_row_id: true,
1743 with_row_addr: false,
1744 with_row_last_updated_at_version: true,
1745 with_row_created_at_version: true,
1746 deletion_vector: Some(Arc::new(DeletionVector::Bitmap(deletions))),
1747 row_id_sequence: None,
1748 last_updated_at_sequence: Some(last_updated_seq),
1749 created_at_sequence: Some(created_seq),
1750 make_deletions_null: false,
1751 total_num_rows: 100,
1752 };
1753 let stream = super::wrap_with_row_id_and_delete(data, 0, config);
1754 let batches: Vec<_> = stream
1755 .buffered(8)
1756 .try_filter(|b| std::future::ready(b.num_rows() > 0))
1757 .try_collect()
1758 .await
1759 .unwrap();
1760
1761 let total_rows: usize = batches.iter().map(|b| b.num_rows()).sum();
1762 assert_eq!(total_rows, 60);
1763
1764 let created_versions: Vec<u64> = batches
1765 .iter()
1766 .flat_map(|b| {
1767 b.column_by_name("_row_created_at_version")
1768 .unwrap()
1769 .as_primitive::<UInt64Type>()
1770 .values()
1771 .to_vec()
1772 })
1773 .collect();
1774 let last_updated_versions: Vec<u64> = batches
1775 .iter()
1776 .flat_map(|b| {
1777 b.column_by_name("_row_last_updated_at_version")
1778 .unwrap()
1779 .as_primitive::<UInt64Type>()
1780 .values()
1781 .to_vec()
1782 })
1783 .collect();
1784 let surviving_positions: Vec<u64> = (20..60).chain(80..100).collect();
1785 let expected_created: Vec<u64> = surviving_positions
1786 .iter()
1787 .map(|position| 1_000 + position)
1788 .collect();
1789 let expected_last_updated: Vec<u64> = surviving_positions
1790 .iter()
1791 .map(|position| match position {
1792 0..=6 => 11,
1793 7..=19 => 22,
1794 20 => 33,
1795 21..=49 => 44,
1796 _ => 55,
1797 })
1798 .collect();
1799
1800 assert_eq!(created_versions, expected_created);
1801 assert_eq!(last_updated_versions, expected_last_updated);
1802 }
1803
1804 #[tokio::test]
1805 async fn test_version_column_with_unsorted_indices_across_batches() {
1806 use crate::rowids::segment::U64Segment;
1807 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
1808
1809 let sequence = Arc::new(RowDatasetVersionSequence {
1810 runs: (0..10)
1811 .map(|position| RowDatasetVersionRun {
1812 span: U64Segment::Range(position..position + 1),
1813 version: 100 + position,
1814 })
1815 .collect(),
1816 });
1817 let indices = UInt32Array::from(vec![8, 2, 9, 1, 6]);
1818 let batches = [2, 2, 1].into_iter().map(|num_rows| ReadBatchTask {
1819 num_rows,
1820 task: std::future::ready(Ok(arrow_array::record_batch!((
1821 "x",
1822 Int32,
1823 vec![0; num_rows as usize]
1824 ))
1825 .unwrap()))
1826 .boxed(),
1827 });
1828 let config = RowIdAndDeletesConfig {
1829 params: ReadBatchParams::Indices(indices.clone()),
1830 with_row_id: false,
1831 with_row_addr: false,
1832 with_row_last_updated_at_version: true,
1833 with_row_created_at_version: false,
1834 deletion_vector: None,
1835 row_id_sequence: None,
1836 last_updated_at_sequence: Some(sequence),
1837 created_at_sequence: None,
1838 make_deletions_null: false,
1839 total_num_rows: 10,
1840 };
1841
1842 let actual = super::wrap_with_row_id_and_delete(stream::iter(batches).boxed(), 0, config)
1843 .buffered(3)
1844 .try_collect::<Vec<_>>()
1845 .await
1846 .unwrap()
1847 .iter()
1848 .flat_map(|batch| {
1849 batch["_row_last_updated_at_version"]
1850 .as_primitive::<UInt64Type>()
1851 .values()
1852 })
1853 .copied()
1854 .collect::<Vec<_>>();
1855 let expected = indices
1856 .values()
1857 .iter()
1858 .map(|position| 100 + u64::from(*position))
1859 .collect::<Vec<_>>();
1860 assert_eq!(actual, expected);
1861 }
1862
1863 #[test]
1864 fn test_apply_version_column_direct_call_fallback() {
1865 use crate::rowids::segment::U64Segment;
1866 use crate::rowids::version::{RowDatasetVersionRun, RowDatasetVersionSequence};
1867
1868 let sequence = Arc::new(RowDatasetVersionSequence {
1869 runs: (0..5)
1870 .map(|position| RowDatasetVersionRun {
1871 span: U64Segment::Range(position..position + 1),
1872 version: 10 + position,
1873 })
1874 .collect(),
1875 });
1876 let config = RowIdAndDeletesConfig {
1877 params: ReadBatchParams::Indices(UInt32Array::from(vec![4, 1, 3])),
1878 with_row_id: false,
1879 with_row_addr: false,
1880 with_row_last_updated_at_version: true,
1881 with_row_created_at_version: false,
1882 deletion_vector: None,
1883 row_id_sequence: None,
1884 last_updated_at_sequence: Some(sequence),
1885 created_at_sequence: None,
1886 make_deletions_null: false,
1887 total_num_rows: 5,
1888 };
1889 let batch = arrow_array::record_batch!(("x", Int32, vec![0; 3])).unwrap();
1890
1891 let actual = super::apply_row_id_and_deletes(batch, 0, 0, &config).unwrap();
1892 assert_eq!(
1893 actual["_row_last_updated_at_version"]
1894 .as_primitive::<UInt64Type>()
1895 .values(),
1896 &[14, 11, 13]
1897 );
1898 }
1899}