1use std::{ops::Range, sync::Arc};
11
12use lance_core::Error;
13use lance_core::Result;
14use lance_core::deepsize::DeepSizeOf;
15use prost::Message;
16use serde::de::Deserializer;
17use serde::ser::Serializer;
18use serde::{Deserialize, Serialize};
19
20use crate::format::{ExternalFile, Fragment, pb};
21use crate::rowids::segment::U64Segment;
22use crate::rowids::{RowIdSequence, read_row_ids};
23
24#[derive(Debug, Clone, PartialEq, Eq, DeepSizeOf)]
30pub struct RowDatasetVersionRun {
31 pub span: U64Segment,
32 pub version: u64,
33}
34
35impl RowDatasetVersionRun {
36 pub fn len(&self) -> usize {
38 self.span.len()
39 }
40
41 pub fn is_empty(&self) -> bool {
43 self.span.is_empty()
44 }
45
46 pub fn version(&self) -> u64 {
48 self.version
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, DeepSizeOf, Default)]
58pub struct RowDatasetVersionSequence {
59 pub runs: Vec<RowDatasetVersionRun>,
60}
61
62#[derive(Debug, Default)]
69pub(crate) struct RowDatasetVersionCursor {
70 run_index: usize,
71 offset_in_run: usize,
72 position: usize,
73 run_len: Option<usize>,
74 run_offsets: Option<Vec<usize>>,
75 indexed_total_len: usize,
76 #[cfg(test)]
77 indexed_seek_count: usize,
78}
79
80impl RowDatasetVersionCursor {
81 fn seek_indexed(
82 &mut self,
83 sequence: &RowDatasetVersionSequence,
84 position: usize,
85 ) -> Result<()> {
86 if self.run_offsets.is_none() {
87 let mut total_len = 0;
88 let run_offsets = sequence
89 .runs
90 .iter()
91 .map(|run| {
92 let offset = total_len;
93 total_len += run.len();
94 offset
95 })
96 .collect();
97 self.run_offsets = Some(run_offsets);
98 self.indexed_total_len = total_len;
99 }
100
101 if position >= self.indexed_total_len {
102 return Err(Error::internal(format!(
103 "version column position {} out of range (total_len={})",
104 position, self.indexed_total_len
105 )));
106 }
107
108 let run_offsets = self.run_offsets.as_ref().unwrap();
109 let mut run_index = match run_offsets.binary_search(&position) {
110 Ok(run_index) => run_index,
111 Err(run_index) => run_index - 1,
112 };
113 while run_index + 1 < run_offsets.len() && run_offsets[run_index + 1] <= position {
114 run_index += 1;
115 }
116 self.run_index = run_index;
117 self.offset_in_run = position - run_offsets[run_index];
118 self.position = position;
119 self.run_len = None;
120 #[cfg(test)]
121 {
122 self.indexed_seek_count += 1;
123 }
124 Ok(())
125 }
126
127 fn current_run<'a>(
128 &mut self,
129 sequence: &'a RowDatasetVersionSequence,
130 ) -> Option<(&'a RowDatasetVersionRun, usize)> {
131 loop {
132 let run = sequence.runs.get(self.run_index)?;
133 let run_len = *self.run_len.get_or_insert_with(|| match &run.span {
134 U64Segment::Range(range) => (range.end - range.start) as usize,
137 span => span.len(),
138 });
139 if self.offset_in_run < run_len {
140 return Some((run, run_len));
141 }
142 self.run_index += 1;
143 self.offset_in_run = 0;
144 self.run_len = None;
145 }
146 }
147
148 pub(crate) fn extend_range(
150 &mut self,
151 sequence: &RowDatasetVersionSequence,
152 selection: Range<usize>,
153 versions: &mut Vec<u64>,
154 ) -> Result<()> {
155 if selection.is_empty() {
156 return Ok(());
157 }
158 if selection.start < self.position
159 || (self.run_offsets.is_some() && selection.start != self.position)
160 {
161 self.seek_indexed(sequence, selection.start)?;
162 }
163
164 while self.position < selection.start {
165 let Some((_, run_len)) = self.current_run(sequence) else {
166 return Err(Error::internal(format!(
167 "version column position {} out of range (total_len={})",
168 selection.start, self.position
169 )));
170 };
171 let advance = (selection.start - self.position).min(run_len - self.offset_in_run);
172 self.offset_in_run += advance;
173 self.position += advance;
174 }
175
176 while self.position < selection.end {
177 let Some((run, run_len)) = self.current_run(sequence) else {
178 return Err(Error::internal(format!(
179 "version column position {} out of range (total_len={})",
180 self.position, self.position
181 )));
182 };
183 let count = (selection.end - self.position).min(run_len - self.offset_in_run);
184 versions.extend(std::iter::repeat_n(run.version(), count));
185 self.offset_in_run += count;
186 self.position += count;
187 }
188 Ok(())
189 }
190}
191
192impl RowDatasetVersionSequence {
193 pub fn new() -> Self {
195 Self { runs: Vec::new() }
196 }
197
198 pub fn from_uniform_row_count(row_count: u64, version: u64) -> Self {
200 if row_count == 0 {
201 return Self::new();
202 }
203 let run = RowDatasetVersionRun {
204 span: U64Segment::Range(0..row_count),
205 version,
206 };
207 Self { runs: vec![run] }
208 }
209
210 pub fn len(&self) -> u64 {
212 self.runs.iter().map(|s| s.len() as u64).sum()
213 }
214
215 pub fn is_empty(&self) -> bool {
217 self.runs.is_empty() || self.runs.iter().all(|s| s.is_empty())
218 }
219
220 pub fn versions(&self) -> VersionsIter<'_> {
222 VersionsIter::new(&self.runs)
223 }
224
225 pub(crate) fn cursor(&self) -> RowDatasetVersionCursor {
227 RowDatasetVersionCursor::default()
228 }
229
230 pub fn version_at(&self, index: usize) -> Option<u64> {
232 let mut offset = 0usize;
233 for run in &self.runs {
234 let len = run.len();
235 if index < offset + len {
236 return Some(run.version());
237 }
238 offset += len;
239 }
240 None
241 }
242
243 pub fn get_version_for_row_id(&self, row_ids: &RowIdSequence, row_id: u64) -> Option<u64> {
247 let mut offset = 0usize;
248 for seg in &row_ids.0 {
249 if seg.range().is_some_and(|r| r.contains(&row_id))
250 && let Some(local) = seg.position(row_id)
251 {
252 return self.version_at(offset + local);
253 }
254 offset += seg.len();
255 }
256 None
257 }
258
259 pub fn rows_with_version_greater_than(
261 &self,
262 row_ids: &RowIdSequence,
263 threshold: u64,
264 ) -> Vec<u64> {
265 row_ids
266 .iter()
267 .zip(self.versions())
268 .filter_map(|(rid, v)| if v > threshold { Some(rid) } else { None })
269 .collect()
270 }
271
272 pub fn mask(&mut self, positions: impl IntoIterator<Item = u32>) -> Result<()> {
274 let mut local_positions: Vec<u32> = Vec::new();
275 let mut positions_iter = positions.into_iter();
276 let mut curr_position = positions_iter.next();
277 let mut offset: usize = 0;
278 let mut cutoff: usize = 0;
279
280 for run in self.runs.iter_mut() {
281 cutoff += run.span.len();
282 while let Some(position) = curr_position {
283 if position as usize >= cutoff {
284 break;
285 }
286 local_positions.push(position - offset as u32);
287 curr_position = positions_iter.next();
288 }
289
290 if !local_positions.is_empty() {
291 run.span.mask(local_positions.as_slice());
292 local_positions.clear();
293 }
294 offset = cutoff;
295 }
296
297 self.runs.retain(|r| !r.span.is_empty());
298 Ok(())
299 }
300}
301
302pub struct VersionsIter<'a> {
304 runs: &'a [RowDatasetVersionRun],
305 run_idx: usize,
306 remaining_in_run: usize,
307 current_version: u64,
308}
309
310impl<'a> VersionsIter<'a> {
311 fn new(runs: &'a [RowDatasetVersionRun]) -> Self {
312 let mut it = Self {
313 runs,
314 run_idx: 0,
315 remaining_in_run: 0,
316 current_version: 0,
317 };
318 it.advance_run();
319 it
320 }
321
322 fn advance_run(&mut self) {
323 if self.run_idx < self.runs.len() {
324 let run = &self.runs[self.run_idx];
325 self.remaining_in_run = run.len();
326 self.current_version = run.version();
327 } else {
328 self.remaining_in_run = 0;
329 }
330 }
331}
332
333impl<'a> Iterator for VersionsIter<'a> {
334 type Item = u64;
335
336 fn next(&mut self) -> Option<Self::Item> {
337 if self.remaining_in_run == 0 {
338 self.run_idx += 1;
340 if self.run_idx >= self.runs.len() {
341 return None;
342 }
343 self.advance_run();
344 }
345 self.remaining_in_run = self.remaining_in_run.saturating_sub(1);
346 Some(self.current_version)
347 }
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, DeepSizeOf)]
356pub enum RowDatasetVersionMeta {
357 Inline(Arc<[u8]>),
359 External(ExternalFile),
361}
362
363impl Serialize for RowDatasetVersionMeta {
365 fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
366 #[derive(Serialize)]
367 #[serde(untagged)]
368 enum Helper<'a> {
369 Inline { inline: &'a [u8] },
370 External { external: &'a ExternalFile },
371 }
372
373 match self {
374 Self::Inline(data) => Helper::Inline {
375 inline: data.as_ref(),
376 }
377 .serialize(serializer),
378 Self::External(file) => Helper::External { external: file }.serialize(serializer),
379 }
380 }
381}
382
383impl<'de> Deserialize<'de> for RowDatasetVersionMeta {
385 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
386 #[derive(Deserialize)]
387 #[serde(untagged)]
388 enum Helper {
389 Inline { inline: Vec<u8> },
390 External { external: ExternalFile },
391 }
392
393 match Helper::deserialize(deserializer)? {
394 Helper::Inline { inline } => Ok(Self::Inline(Arc::from(inline))),
395 Helper::External { external } => Ok(Self::External(external)),
396 }
397 }
398}
399
400impl RowDatasetVersionMeta {
401 pub fn from_sequence(sequence: &RowDatasetVersionSequence) -> lance_core::Result<Self> {
403 let bytes = write_dataset_versions(sequence);
404 Ok(Self::Inline(Arc::from(bytes)))
405 }
406
407 pub fn from_external_file(path: String, offset: u64, size: u64) -> Self {
409 Self::External(ExternalFile { path, offset, size })
410 }
411
412 pub fn load_sequence(&self) -> lance_core::Result<RowDatasetVersionSequence> {
414 match self {
415 Self::Inline(data) => read_dataset_versions(data),
416 Self::External(_file) => {
417 todo!("External file loading not yet implemented")
418 }
419 }
420 }
421}
422
423pub fn last_updated_at_version_meta_to_pb(
425 meta: &Option<RowDatasetVersionMeta>,
426) -> Option<pb::data_fragment::LastUpdatedAtVersionSequence> {
427 meta.as_ref().map(|m| match m {
428 RowDatasetVersionMeta::Inline(data) => {
429 pb::data_fragment::LastUpdatedAtVersionSequence::InlineLastUpdatedAtVersions(
430 data.to_vec(),
431 )
432 }
433 RowDatasetVersionMeta::External(file) => {
434 pb::data_fragment::LastUpdatedAtVersionSequence::ExternalLastUpdatedAtVersions(
435 pb::ExternalFile {
436 path: file.path.clone(),
437 offset: file.offset,
438 size: file.size,
439 },
440 )
441 }
442 })
443}
444
445pub fn created_at_version_meta_to_pb(
447 meta: &Option<RowDatasetVersionMeta>,
448) -> Option<pb::data_fragment::CreatedAtVersionSequence> {
449 meta.as_ref().map(|m| match m {
450 RowDatasetVersionMeta::Inline(data) => {
451 pb::data_fragment::CreatedAtVersionSequence::InlineCreatedAtVersions(data.to_vec())
452 }
453 RowDatasetVersionMeta::External(file) => {
454 pb::data_fragment::CreatedAtVersionSequence::ExternalCreatedAtVersions(
455 pb::ExternalFile {
456 path: file.path.clone(),
457 offset: file.offset,
458 size: file.size,
459 },
460 )
461 }
462 })
463}
464
465pub fn write_dataset_versions(sequence: &RowDatasetVersionSequence) -> Vec<u8> {
467 let pb_sequence = pb::RowDatasetVersionSequence {
469 runs: sequence
470 .runs
471 .iter()
472 .map(|run| pb::RowDatasetVersionRun {
473 span: Some(pb::U64Segment::from(run.span.clone())),
474 version: run.version,
475 })
476 .collect(),
477 };
478
479 pb_sequence.encode_to_vec()
480}
481
482pub fn read_dataset_versions(data: &[u8]) -> lance_core::Result<RowDatasetVersionSequence> {
484 let pb_sequence = pb::RowDatasetVersionSequence::decode(data).map_err(|e| {
485 Error::internal(format!("Failed to decode RowDatasetVersionSequence: {}", e))
486 })?;
487
488 let segments = pb_sequence
489 .runs
490 .into_iter()
491 .map(|pb_run| {
492 let positions_pb = pb_run.span.ok_or_else(|| {
493 Error::internal("Missing positions in RowDatasetVersionRun".to_string())
494 })?;
495 let segment = U64Segment::try_from(positions_pb)?;
496 Ok(RowDatasetVersionRun {
497 span: segment,
498 version: pb_run.version,
499 })
500 })
501 .collect::<Result<Vec<_>>>()?;
502
503 Ok(RowDatasetVersionSequence { runs: segments })
504}
505
506pub fn rechunk_version_sequences(
508 sequences: impl IntoIterator<Item = RowDatasetVersionSequence>,
509 chunk_sizes: impl IntoIterator<Item = u64>,
510 allow_incomplete: bool,
511) -> Result<Vec<RowDatasetVersionSequence>> {
512 let chunk_sizes_vec: Vec<u64> = chunk_sizes.into_iter().collect();
513 let total_chunks = chunk_sizes_vec.len();
514 let mut chunked_sequences: Vec<RowDatasetVersionSequence> = Vec::with_capacity(total_chunks);
515
516 let mut run_iter = sequences
517 .into_iter()
518 .flat_map(|sequence| sequence.runs.into_iter())
519 .peekable();
520
521 let too_few_segments_error = |chunk_index: usize, expected_chunk_size: u64, remaining: u64| {
522 Error::invalid_input(format!(
523 "Got too few version runs for chunk {}. Expected chunk size: {}, remaining needed: {}",
524 chunk_index, expected_chunk_size, remaining
525 ))
526 };
527
528 let too_many_segments_error = |processed_chunks: usize, total_chunk_sizes: usize| {
529 Error::invalid_input(format!(
530 "Got too many version runs for the provided chunk lengths. Processed {} chunks out of {} expected",
531 processed_chunks, total_chunk_sizes
532 ))
533 };
534
535 let mut segment_offset = 0_u64;
536
537 for (chunk_index, chunk_size) in chunk_sizes_vec.iter().enumerate() {
538 let chunk_size = *chunk_size;
539 let mut out_seq = RowDatasetVersionSequence::new();
540 let mut remaining = chunk_size;
541
542 while remaining > 0 {
543 let remaining_in_segment = run_iter
544 .peek()
545 .map_or(0, |run| run.span.len() as u64 - segment_offset);
546
547 if remaining_in_segment == 0 {
548 if run_iter.next().is_some() {
549 segment_offset = 0;
550 continue;
551 } else if allow_incomplete {
552 break;
553 } else {
554 return Err(too_few_segments_error(chunk_index, chunk_size, remaining));
555 }
556 }
557
558 match remaining_in_segment.cmp(&remaining) {
559 std::cmp::Ordering::Greater => {
560 let run = run_iter.peek().unwrap();
561 let seg = run.span.slice(segment_offset as usize, remaining as usize);
562 out_seq.runs.push(RowDatasetVersionRun {
563 span: seg,
564 version: run.version,
565 });
566 segment_offset += remaining;
567 remaining = 0;
568 }
569 std::cmp::Ordering::Equal | std::cmp::Ordering::Less => {
570 let run = run_iter.next().ok_or_else(|| {
571 too_few_segments_error(chunk_index, chunk_size, remaining)
572 })?;
573 let seg = run
574 .span
575 .slice(segment_offset as usize, remaining_in_segment as usize);
576 out_seq.runs.push(RowDatasetVersionRun {
577 span: seg,
578 version: run.version,
579 });
580 segment_offset = 0;
581 remaining -= remaining_in_segment;
582 }
583 }
584 }
585
586 chunked_sequences.push(out_seq);
587 }
588
589 if run_iter.peek().is_some() {
590 return Err(too_many_segments_error(
591 chunked_sequences.len(),
592 total_chunks,
593 ));
594 }
595
596 Ok(chunked_sequences)
597}
598
599pub fn build_version_meta(
601 fragment: &Fragment,
602 current_version: u64,
603) -> Option<RowDatasetVersionMeta> {
604 if let Some(physical_rows) = fragment.physical_rows
605 && physical_rows > 0
606 {
607 if fragment.row_id_meta.is_none() {
609 panic!("Can not find row id meta, please make sure you have enabled stable row id.")
610 }
611
612 let version_sequence = RowDatasetVersionSequence::from_uniform_row_count(
616 physical_rows as u64,
617 current_version,
618 );
619
620 return Some(RowDatasetVersionMeta::from_sequence(&version_sequence).unwrap());
621 }
622 None
623}
624
625pub fn refresh_row_latest_update_meta_for_full_frag_rewrite_cols(
629 fragment: &mut Fragment,
630 current_version: u64,
631) -> Result<()> {
632 let row_count = if let Some(pr) = fragment.physical_rows {
633 pr as u64
634 } else if let Some(row_id_meta) = fragment.row_id_meta.as_ref() {
635 match row_id_meta {
636 crate::format::RowIdMeta::Inline(data) => {
637 let sequence = read_row_ids(data).unwrap();
638 sequence.len()
639 }
640 crate::format::RowIdMeta::External(_file) => 0,
642 }
643 } else {
644 0
645 };
646
647 if row_count > 0 {
648 let version_seq =
649 RowDatasetVersionSequence::from_uniform_row_count(row_count, current_version);
650 let version_meta = RowDatasetVersionMeta::from_sequence(&version_seq)?;
651 fragment.last_updated_at_version_meta = Some(version_meta);
652 }
653
654 Ok(())
655}
656
657pub fn refresh_row_latest_update_meta_for_partial_frag_rewrite_cols(
663 fragment: &mut Fragment,
664 updated_offsets: &[usize],
665 current_version: u64,
666 prev_version: u64,
667) -> Result<()> {
668 let row_count_u64: u64 = if let Some(pr) = fragment.physical_rows {
670 pr as u64
671 } else if let Some(row_id_meta) = fragment.row_id_meta.as_ref() {
672 match row_id_meta {
673 crate::format::RowIdMeta::Inline(data) => {
674 let sequence = read_row_ids(data).unwrap();
675 sequence.len()
676 }
677 crate::format::RowIdMeta::External(_file) => {
678 todo!("External file loading not yet implemented")
680 }
681 }
682 } else {
683 0
684 };
685
686 if row_count_u64 > 0 {
687 let mut base_versions: Vec<u64> = Vec::with_capacity(row_count_u64 as usize);
689 if let Some(meta) = fragment.last_updated_at_version_meta.as_ref() {
690 if let Ok(base_seq) = meta.load_sequence() {
691 base_versions.extend(base_seq.versions().take(row_count_u64 as usize));
692 base_versions.resize(row_count_u64 as usize, prev_version);
693 } else {
694 base_versions.resize(row_count_u64 as usize, prev_version);
695 }
696 } else {
697 base_versions.resize(row_count_u64 as usize, prev_version);
698 }
699
700 for &pos in updated_offsets {
702 if pos < base_versions.len() {
703 base_versions[pos] = current_version;
704 }
705 }
706
707 let mut runs: Vec<RowDatasetVersionRun> = Vec::new();
709 if !base_versions.is_empty() {
710 let mut start = 0usize;
711 let mut curr_ver = base_versions[0];
712 for (idx, &ver) in base_versions.iter().enumerate().skip(1) {
713 if ver != curr_ver {
714 runs.push(RowDatasetVersionRun {
715 span: U64Segment::Range(start as u64..idx as u64),
716 version: curr_ver,
717 });
718 start = idx;
719 curr_ver = ver;
720 }
721 }
722 runs.push(RowDatasetVersionRun {
723 span: U64Segment::Range(start as u64..base_versions.len() as u64),
724 version: curr_ver,
725 });
726 }
727 let new_seq = RowDatasetVersionSequence { runs };
728 let new_meta = RowDatasetVersionMeta::from_sequence(&new_seq)?;
729 fragment.last_updated_at_version_meta = Some(new_meta);
730 }
731
732 Ok(())
733}
734
735impl TryFrom<pb::data_fragment::LastUpdatedAtVersionSequence> for RowDatasetVersionMeta {
737 type Error = Error;
738
739 fn try_from(value: pb::data_fragment::LastUpdatedAtVersionSequence) -> Result<Self> {
740 match value {
741 pb::data_fragment::LastUpdatedAtVersionSequence::InlineLastUpdatedAtVersions(data) => {
742 Ok(Self::Inline(Arc::from(data)))
743 }
744 pb::data_fragment::LastUpdatedAtVersionSequence::ExternalLastUpdatedAtVersions(
745 file,
746 ) => Ok(Self::External(ExternalFile {
747 path: file.path,
748 offset: file.offset,
749 size: file.size,
750 })),
751 }
752 }
753}
754
755impl TryFrom<pb::data_fragment::CreatedAtVersionSequence> for RowDatasetVersionMeta {
756 type Error = Error;
757
758 fn try_from(value: pb::data_fragment::CreatedAtVersionSequence) -> Result<Self> {
759 match value {
760 pb::data_fragment::CreatedAtVersionSequence::InlineCreatedAtVersions(data) => {
761 Ok(Self::Inline(Arc::from(data)))
762 }
763 pb::data_fragment::CreatedAtVersionSequence::ExternalCreatedAtVersions(file) => {
764 Ok(Self::External(ExternalFile {
765 path: file.path,
766 offset: file.offset,
767 size: file.size,
768 }))
769 }
770 }
771 }
772}
773
774#[cfg(test)]
775mod tests {
776 use super::*;
777
778 #[test]
779 fn test_version_random_access() {
780 let seq = RowDatasetVersionSequence {
781 runs: vec![
782 RowDatasetVersionRun {
783 span: U64Segment::Range(0..3),
784 version: 1,
785 },
786 RowDatasetVersionRun {
787 span: U64Segment::Range(0..2),
788 version: 2,
789 },
790 RowDatasetVersionRun {
791 span: U64Segment::Range(0..1),
792 version: 3,
793 },
794 ],
795 };
796 assert_eq!(seq.version_at(0), Some(1));
797 assert_eq!(seq.version_at(2), Some(1));
798 assert_eq!(seq.version_at(3), Some(2));
799 assert_eq!(seq.version_at(4), Some(2));
800 assert_eq!(seq.version_at(5), Some(3));
801 assert_eq!(seq.version_at(6), None);
802 }
803
804 #[test]
805 fn test_partial_refresh_streams_many_lineage_runs() {
806 const ROWS: usize = 10_000;
807 let prior_sequence = RowDatasetVersionSequence {
808 runs: (0..ROWS)
809 .map(|position| RowDatasetVersionRun {
810 span: U64Segment::Range(position as u64..position as u64 + 1),
811 version: (position % 2 + 1) as u64,
812 })
813 .collect(),
814 };
815 let mut fragment = Fragment::new(1);
816 fragment.physical_rows = Some(ROWS);
817 fragment.last_updated_at_version_meta =
818 Some(RowDatasetVersionMeta::from_sequence(&prior_sequence).unwrap());
819
820 refresh_row_latest_update_meta_for_partial_frag_rewrite_cols(
821 &mut fragment,
822 &[ROWS - 1],
823 3,
824 1,
825 )
826 .unwrap();
827
828 let refreshed = fragment
829 .last_updated_at_version_meta
830 .unwrap()
831 .load_sequence()
832 .unwrap();
833 assert_eq!(refreshed.len(), ROWS as u64);
834 assert_eq!(refreshed.version_at(0), Some(1));
835 assert_eq!(refreshed.version_at(1), Some(2));
836 assert_eq!(refreshed.version_at(ROWS - 2), Some(1));
837 assert_eq!(refreshed.version_at(ROWS - 1), Some(3));
838 }
839
840 #[test]
841 fn test_serialization_round_trip() {
842 let seq = RowDatasetVersionSequence {
843 runs: vec![
844 RowDatasetVersionRun {
845 span: U64Segment::Range(0..4),
846 version: 42,
847 },
848 RowDatasetVersionRun {
849 span: U64Segment::Range(0..3),
850 version: 99,
851 },
852 ],
853 };
854 let bytes = write_dataset_versions(&seq);
855 let seq2 = read_dataset_versions(&bytes).unwrap();
856 assert_eq!(seq2.runs.len(), 2);
857 assert_eq!(seq2.len(), 7);
858 assert_eq!(seq2.version_at(0), Some(42));
859 assert_eq!(seq2.version_at(5), Some(99));
860 }
861
862 #[test]
863 fn test_get_version_for_row_id() {
864 let seq = RowDatasetVersionSequence {
865 runs: vec![
866 RowDatasetVersionRun {
867 span: U64Segment::Range(0..2),
868 version: 8,
869 },
870 RowDatasetVersionRun {
871 span: U64Segment::Range(0..2),
872 version: 9,
873 },
874 ],
875 };
876 let rows = RowIdSequence::from(10..14); assert_eq!(seq.get_version_for_row_id(&rows, 10), Some(8));
878 assert_eq!(seq.get_version_for_row_id(&rows, 11), Some(8));
879 assert_eq!(seq.get_version_for_row_id(&rows, 12), Some(9));
880 assert_eq!(seq.get_version_for_row_id(&rows, 13), Some(9));
881 assert_eq!(seq.get_version_for_row_id(&rows, 99), None);
882 }
883
884 #[test]
885 fn test_version_cursor_ranges_gaps_and_rewind() {
886 let seq = RowDatasetVersionSequence {
887 runs: vec![
888 RowDatasetVersionRun {
889 span: U64Segment::Range(0..3),
890 version: 10,
891 },
892 RowDatasetVersionRun {
893 span: U64Segment::Range(3..3),
894 version: 99,
895 },
896 RowDatasetVersionRun {
897 span: U64Segment::Range(3..5),
898 version: 20,
899 },
900 RowDatasetVersionRun {
901 span: U64Segment::Range(5..9),
902 version: 30,
903 },
904 ],
905 };
906 let expected = [10, 10, 10, 20, 20, 30, 30, 30, 30];
907 let mut cursor = seq.cursor();
908 let mut actual = Vec::new();
909
910 cursor.extend_range(&seq, 0..2, &mut actual).unwrap();
911 cursor.extend_range(&seq, 2..5, &mut actual).unwrap();
912 cursor.extend_range(&seq, 7..9, &mut actual).unwrap();
913 assert_eq!(actual, [10, 10, 10, 20, 20, 30, 30]);
914
915 actual.clear();
916 cursor.extend_range(&seq, 1..6, &mut actual).unwrap();
917 assert_eq!(actual, expected[1..6]);
918
919 cursor.extend_range(&seq, 6..6, &mut actual).unwrap();
920 assert_eq!(actual, expected[1..6]);
921 }
922
923 #[test]
924 fn test_version_cursor_descending_ranges_use_indexed_seek() {
925 const RUNS: usize = 10_000;
926 let seq = RowDatasetVersionSequence {
927 runs: (0..RUNS)
928 .map(|position| RowDatasetVersionRun {
929 span: U64Segment::Range(position as u64..position as u64 + 1),
930 version: position as u64,
931 })
932 .collect(),
933 };
934 let mut cursor = seq.cursor();
935 let mut actual = Vec::with_capacity(RUNS);
936
937 for position in (0..RUNS).rev() {
938 cursor
939 .extend_range(&seq, position..position + 1, &mut actual)
940 .unwrap();
941 }
942
943 assert_eq!(actual, (0..RUNS as u64).rev().collect::<Vec<_>>());
944 assert_eq!(cursor.run_offsets.as_ref().unwrap().len(), RUNS);
945 }
946
947 #[test]
948 fn test_version_cursor_alternating_ranges_use_indexed_seek_both_directions() {
949 const RUNS: usize = 10_000;
950 let seq = RowDatasetVersionSequence {
951 runs: (0..RUNS)
952 .map(|position| RowDatasetVersionRun {
953 span: U64Segment::Range(position as u64..position as u64 + 1),
954 version: position as u64,
955 })
956 .collect(),
957 };
958 let mut cursor = seq.cursor();
959 let mut actual = Vec::with_capacity(RUNS);
960
961 for selection_index in 0..RUNS {
962 let position = if selection_index % 2 == 0 {
963 RUNS - 1
964 } else {
965 0
966 };
967 cursor
968 .extend_range(&seq, position..position + 1, &mut actual)
969 .unwrap();
970 }
971
972 let expected = (0..RUNS)
973 .map(|selection_index| {
974 if selection_index % 2 == 0 {
975 (RUNS - 1) as u64
976 } else {
977 0
978 }
979 })
980 .collect::<Vec<_>>();
981 assert_eq!(actual, expected);
982 assert_eq!(cursor.run_offsets.as_ref().unwrap().len(), RUNS);
983 assert_eq!(cursor.indexed_seek_count, RUNS - 1);
984 }
985
986 #[test]
987 fn test_version_cursor_reports_out_of_bounds() {
988 let seq = RowDatasetVersionSequence::from_uniform_row_count(4, 7);
989 let mut cursor = seq.cursor();
990 let mut actual = Vec::new();
991 let error = cursor.extend_range(&seq, 4..5, &mut actual).unwrap_err();
992 assert!(matches!(error, Error::Internal { .. }));
993 assert!(
994 error
995 .to_string()
996 .contains("position 4 out of range (total_len=4)")
997 );
998 assert!(actual.is_empty());
999 }
1000
1001 #[test]
1002 fn test_version_cursor_non_range_span() {
1003 let non_range_span = U64Segment::from_slice(&[0, 2, 4, 6, 8]);
1004 assert!(!matches!(non_range_span, U64Segment::Range(_)));
1005 let seq = RowDatasetVersionSequence {
1006 runs: vec![
1007 RowDatasetVersionRun {
1008 span: non_range_span,
1009 version: 4,
1010 },
1011 RowDatasetVersionRun {
1012 span: U64Segment::Range(0..3),
1013 version: 8,
1014 },
1015 ],
1016 };
1017 let mut actual = Vec::new();
1018 seq.cursor().extend_range(&seq, 0..8, &mut actual).unwrap();
1019 assert_eq!(actual, [4, 4, 4, 4, 4, 8, 8, 8]);
1020 }
1021}