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