1use crate::{format::pb, rowids::bitmap::Bitmap};
5use lance_core::{Error, Result};
6
7use super::{RowIdSequence, U64Segment, encoded_array::EncodedU64Array};
8use std::ops::Range;
9
10use prost::Message;
11use prost::encoding::{WireType, decode_key, decode_varint};
12
13const ROW_ID_METADATA: &str = "row ID metadata";
14
15pub(super) fn corrupt_row_id_metadata(message: impl Into<String>) -> Error {
16 Error::corrupt_file_named(ROW_ID_METADATA, message)
17}
18
19fn validate_range(segment_type: &str, start: u64, end: u64) -> Result<usize> {
20 let len = end.checked_sub(start).ok_or_else(|| {
21 corrupt_row_id_metadata(format!(
22 "{segment_type} range start {start} exceeds end {end}"
23 ))
24 })?;
25 usize::try_from(len).map_err(|_| {
26 corrupt_row_id_metadata(format!(
27 "{segment_type} range length {len} for start {start} and end {end} exceeds usize::MAX"
28 ))
29 })
30}
31
32fn validate_packed_array_length(array_type: &str, byte_len: usize, width: usize) -> Result<()> {
33 if !byte_len.is_multiple_of(width) {
34 return Err(corrupt_row_id_metadata(format!(
35 "encoded {array_type} array byte length {byte_len} is not a multiple of element width {width}"
36 )));
37 }
38 Ok(())
39}
40
41fn first_non_increasing_pair(array: &EncodedU64Array) -> Option<(usize, u64, u64)> {
48 let mut values = array.iter();
49 let previous = values.next()?;
50 values
51 .scan(previous, |previous, value| {
52 let pair = (*previous, value);
53 *previous = value;
54 Some(pair)
55 })
56 .enumerate()
57 .find_map(|(index, (previous, next))| (previous >= next).then_some((index, previous, next)))
58}
59
60fn push_range(pending: &mut Vec<Range<u64>>, out: &mut Vec<U64Segment>, range: Range<u64>) {
69 match pending.last_mut() {
70 Some(last) if last.end == range.start => last.end = range.end,
71 Some(last) if last.end > range.start => {
72 flush_ranges(pending, out);
73 pending.push(range);
74 }
75 _ => pending.push(range),
76 }
77}
78
79fn flush_ranges(pending: &mut Vec<Range<u64>>, out: &mut Vec<U64Segment>) {
80 match U64Segment::from_sorted_ranges(pending.as_slice()) {
81 Some(segment) => out.push(segment),
82 None => out.extend(pending.iter().cloned().map(U64Segment::Range)),
84 }
85 pending.clear();
86}
87
88impl TryFrom<pb::RowIdSequence> for RowIdSequence {
89 type Error = Error;
90
91 fn try_from(pb: pb::RowIdSequence) -> Result<Self> {
94 read_row_ids(&pb.encode_to_vec())
95 }
96}
97
98impl TryFrom<pb::U64Segment> for U64Segment {
99 type Error = Error;
100
101 fn try_from(pb: pb::U64Segment) -> Result<Self> {
102 use pb::u64_segment as pb_seg;
103 use pb::u64_segment::Segment::*;
104 match pb.segment {
105 Some(Range(pb_seg::Range { start, end })) => {
106 validate_range("Range", start, end)?;
107 Ok(Self::Range(start..end))
108 }
109 Some(RangeWithHoles(pb_seg::RangeWithHoles { start, end, holes })) => {
110 validate_range("RangeWithHoles", start, end)?;
111 let holes = holes
112 .ok_or_else(|| {
113 corrupt_row_id_metadata("RangeWithHoles is missing its holes array")
114 })?
115 .try_into()?;
116 if let Some((index, previous, next)) = first_non_increasing_pair(&holes) {
117 return Err(corrupt_row_id_metadata(format!(
118 "RangeWithHoles values are not strictly increasing at indices {index} and {}: {previous} is not less than {next}",
119 index + 1
120 )));
121 }
122 if let Some(hole) = holes.iter().find(|hole| *hole < start || *hole >= end) {
123 return Err(corrupt_row_id_metadata(format!(
124 "RangeWithHoles hole {hole} is outside the range {start}..{end}"
125 )));
126 }
127 Ok(Self::RangeWithHoles {
128 range: start..end,
129 holes,
130 })
131 }
132 Some(RangeWithBitmap(pb_seg::RangeWithBitmap { start, end, bitmap })) => {
133 let range_len = validate_range("RangeWithBitmap", start, end)?;
134 let expected_bitmap_len = range_len.div_ceil(8);
135 if bitmap.len() != expected_bitmap_len {
136 return Err(corrupt_row_id_metadata(format!(
137 "RangeWithBitmap byte length {} does not match expected {expected_bitmap_len} for range start {start}, end {end}, and length {range_len}",
138 bitmap.len()
139 )));
140 }
141 let remainder = range_len % 8;
142 if remainder != 0 {
143 let padding_mask = !((1_u8 << remainder) - 1);
144 let last_byte = bitmap[expected_bitmap_len - 1];
145 if last_byte & padding_mask != 0 {
146 return Err(corrupt_row_id_metadata(format!(
147 "RangeWithBitmap padding bits must be zero for range start {start}, end {end}, and length {range_len}: last byte {last_byte:#04x} has padding mask {padding_mask:#04x} set"
148 )));
149 }
150 }
151 Ok(Self::RangeWithBitmap {
152 range: start..end,
153 bitmap: Bitmap::from_parts(bitmap, range_len),
154 })
155 }
156 Some(SortedArray(array)) => {
157 let array = EncodedU64Array::try_from(array)?;
158 if let Some((index, previous, next)) = first_non_increasing_pair(&array) {
159 return Err(corrupt_row_id_metadata(format!(
160 "SortedArray values are not strictly increasing at indices {index} and {}: {previous} is not less than {next}",
161 index + 1
162 )));
163 }
164 Ok(Self::SortedArray(array))
165 }
166 Some(Array(array)) => Ok(Self::Array(EncodedU64Array::try_from(array)?)),
167 None => Err(corrupt_row_id_metadata("missing row ID segment type")),
170 }
171 }
172}
173
174impl TryFrom<pb::EncodedU64Array> for EncodedU64Array {
175 type Error = Error;
176
177 fn try_from(pb: pb::EncodedU64Array) -> Result<Self> {
178 use pb::encoded_u64_array as pb_arr;
179 use pb::encoded_u64_array::Array::*;
180 match pb.array {
181 Some(U16Array(pb_arr::U16Array { base, offsets })) => {
182 validate_packed_array_length("u16", offsets.len(), 2)?;
183 let offsets = offsets
184 .as_chunks::<2>()
185 .0
186 .iter()
187 .map(|chunk| u16::from_le_bytes(*chunk))
188 .collect::<Vec<_>>();
189 if let Some(max_offset) = offsets.iter().copied().max()
190 && base.checked_add(u64::from(max_offset)).is_none()
191 {
192 return Err(corrupt_row_id_metadata(format!(
193 "U16Array base {base} plus maximum offset {max_offset} overflows u64"
194 )));
195 }
196 Ok(Self::U16 { base, offsets })
197 }
198 Some(U32Array(pb_arr::U32Array { base, offsets })) => {
199 validate_packed_array_length("u32", offsets.len(), 4)?;
200 let offsets = offsets
201 .as_chunks::<4>()
202 .0
203 .iter()
204 .map(|chunk| u32::from_le_bytes(*chunk))
205 .collect::<Vec<_>>();
206 if let Some(max_offset) = offsets.iter().copied().max()
207 && base.checked_add(u64::from(max_offset)).is_none()
208 {
209 return Err(corrupt_row_id_metadata(format!(
210 "U32Array base {base} plus maximum offset {max_offset} overflows u64"
211 )));
212 }
213 Ok(Self::U32 { base, offsets })
214 }
215 Some(U64Array(pb_arr::U64Array { values })) => {
216 validate_packed_array_length("u64", values.len(), 8)?;
217 let values = values
218 .as_chunks::<8>()
219 .0
220 .iter()
221 .map(|chunk| u64::from_le_bytes(*chunk))
222 .collect();
223 Ok(Self::U64(values))
224 }
225 None => Err(corrupt_row_id_metadata("missing encoded row ID array type")),
228 }
229 }
230}
231
232impl From<RowIdSequence> for pb::RowIdSequence {
233 fn from(sequence: RowIdSequence) -> Self {
234 let segments = sequence
235 .0
236 .into_iter()
237 .flat_map(|segment| match segment {
238 U64Segment::Ranges { range, runs } => runs
242 .present_ranges()
243 .map(|present| {
244 U64Segment::Range(
245 range.start + present.start as u64..range.start + present.end as u64,
246 )
247 })
248 .map(pb::U64Segment::from)
249 .collect::<Vec<_>>(),
250 other => vec![pb::U64Segment::from(other)],
251 })
252 .collect();
253 Self { segments }
254 }
255}
256
257impl From<U64Segment> for pb::U64Segment {
258 fn from(segment: U64Segment) -> Self {
259 match segment {
260 U64Segment::Range(range) => Self {
261 segment: Some(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
262 start: range.start,
263 end: range.end,
264 })),
265 },
266 U64Segment::RangeWithHoles { range, holes } => Self {
267 segment: Some(pb::u64_segment::Segment::RangeWithHoles(
268 pb::u64_segment::RangeWithHoles {
269 start: range.start,
270 end: range.end,
271 holes: Some(holes.into()),
272 },
273 )),
274 },
275 U64Segment::RangeWithBitmap { range, bitmap } => Self {
276 segment: Some(pb::u64_segment::Segment::RangeWithBitmap(
277 pb::u64_segment::RangeWithBitmap {
278 start: range.start,
279 end: range.end,
280 bitmap: bitmap.into_bytes(),
281 },
282 )),
283 },
284 U64Segment::Ranges { .. } => {
289 unreachable!("U64Segment::Ranges is only serialized as part of a RowIdSequence")
290 }
291 U64Segment::SortedArray(array) => Self {
292 segment: Some(pb::u64_segment::Segment::SortedArray(array.into())),
293 },
294 U64Segment::Array(array) => Self {
295 segment: Some(pb::u64_segment::Segment::Array(array.into())),
296 },
297 }
298 }
299}
300
301impl From<EncodedU64Array> for pb::EncodedU64Array {
302 fn from(array: EncodedU64Array) -> Self {
303 match array {
304 EncodedU64Array::U16 { base, offsets } => Self {
305 array: Some(pb::encoded_u64_array::Array::U16Array(
306 pb::encoded_u64_array::U16Array {
307 base,
308 offsets: offsets
309 .iter()
310 .flat_map(|&offset| offset.to_le_bytes().to_vec())
311 .collect(),
312 },
313 )),
314 },
315 EncodedU64Array::U32 { base, offsets } => Self {
316 array: Some(pb::encoded_u64_array::Array::U32Array(
317 pb::encoded_u64_array::U32Array {
318 base,
319 offsets: offsets
320 .iter()
321 .flat_map(|&offset| offset.to_le_bytes().to_vec())
322 .collect(),
323 },
324 )),
325 },
326 EncodedU64Array::U64(values) => Self {
327 array: Some(pb::encoded_u64_array::Array::U64Array(
328 pb::encoded_u64_array::U64Array {
329 values: values
330 .iter()
331 .flat_map(|&value| value.to_le_bytes().to_vec())
332 .collect(),
333 },
334 )),
335 },
336 }
337 }
338}
339
340pub fn write_row_ids(sequence: &RowIdSequence) -> Vec<u8> {
342 let pb_sequence = pb::RowIdSequence::from(sequence.clone());
343 pb_sequence.encode_to_vec()
344}
345
346pub fn read_row_ids(mut buf: &[u8]) -> Result<RowIdSequence> {
354 let corrupt = |error: prost::DecodeError| {
355 corrupt_row_id_metadata(format!("failed to decode row ID sequence: {error}"))
356 };
357 let mut segments = Vec::new();
358 let mut pending: Vec<Range<u64>> = Vec::new();
359 while !buf.is_empty() {
360 let (field, wire_type) = decode_key(&mut buf).map_err(corrupt)?;
361 if field != 1 || wire_type != WireType::LengthDelimited {
362 return Err(corrupt_row_id_metadata(format!(
363 "unexpected field {field} with wire type {wire_type:?} in row ID sequence"
364 )));
365 }
366 let len = decode_varint(&mut buf).map_err(corrupt)? as usize;
367 if len > buf.len() {
368 return Err(corrupt_row_id_metadata(format!(
369 "row ID segment of {len} bytes exceeds the {} remaining",
370 buf.len()
371 )));
372 }
373 let (segment, rest) = buf.split_at(len);
374 buf = rest;
375 match plain_range(segment) {
376 Some(range) => push_range(&mut pending, &mut segments, range),
377 None => {
378 flush_ranges(&mut pending, &mut segments);
379 let segment = pb::U64Segment::decode(segment).map_err(corrupt)?;
380 segments.push(U64Segment::try_from(segment)?);
381 }
382 }
383 }
384 flush_ranges(&mut pending, &mut segments);
385 segments
388 .iter()
389 .try_fold(0_u64, |total, segment| {
390 total.checked_add(segment.len() as u64)
391 })
392 .ok_or_else(|| {
393 corrupt_row_id_metadata(format!(
394 "row ID sequence of {} segments has a total length exceeding u64::MAX",
395 segments.len()
396 ))
397 })?;
398 Ok(RowIdSequence(segments))
399}
400
401fn plain_range(mut bytes: &[u8]) -> Option<Range<u64>> {
404 let (field, wire_type) = decode_key(&mut bytes).ok()?;
405 if field != 1 || wire_type != WireType::LengthDelimited {
406 return None;
407 }
408 let len = decode_varint(&mut bytes).ok()? as usize;
409 if len != bytes.len() {
410 return None;
411 }
412 let (mut start, mut end) = (0_u64, 0_u64);
413 while !bytes.is_empty() {
414 let (field, wire_type) = decode_key(&mut bytes).ok()?;
415 if wire_type != WireType::Varint {
416 return None;
417 }
418 let value = decode_varint(&mut bytes).ok()?;
419 match field {
420 1 => start = value,
421 2 => end = value,
422 _ => return None,
423 }
424 }
425 (start < end).then_some(start..end)
426}
427
428#[cfg(test)]
429mod test {
430 use pretty_assertions::assert_eq;
431 use proptest::prelude::*;
432 use rstest::rstest;
433
434 use super::*;
435
436 #[test]
437 fn test_bitmap_serialization_is_byte_exact() {
438 let mut bitmap = Bitmap::new_full(10);
439 bitmap.clear(2);
440 let segment = U64Segment::RangeWithBitmap {
441 range: 100..110,
442 bitmap,
443 };
444 assert_eq!(segment.len(), 9);
445
446 let serialized = pb::U64Segment::from(segment.clone());
447 let Some(pb::u64_segment::Segment::RangeWithBitmap(encoded)) = &serialized.segment else {
448 panic!("expected bitmap segment");
449 };
450 assert_eq!(encoded.bitmap, vec![0xfb, 0x03]);
451 assert_eq!(U64Segment::try_from(serialized).unwrap(), segment);
452 }
453 fn read_segment(segment: pb::u64_segment::Segment) -> Result<RowIdSequence> {
454 let sequence = pb::RowIdSequence {
455 segments: vec![pb::U64Segment {
456 segment: Some(segment),
457 }],
458 };
459 read_row_ids(&sequence.encode_to_vec())
460 }
461
462 fn assert_corrupt_segment(segment: pb::u64_segment::Segment, expected_message: &str) {
463 let error = read_segment(segment).unwrap_err();
464 assert!(matches!(&error, Error::CorruptFile { .. }));
465 assert!(
466 error.to_string().contains(expected_message),
467 "expected error containing {expected_message:?}, got {error}"
468 );
469 }
470
471 #[test]
475 fn test_reject_sequence_length_overflow() {
476 let segment = || pb::U64Segment {
477 segment: Some(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
478 start: 0,
479 end: u64::MAX,
480 })),
481 };
482 let sequence = pb::RowIdSequence {
483 segments: vec![segment(), segment()],
484 };
485
486 let error = read_row_ids(&sequence.encode_to_vec()).unwrap_err();
487 assert!(matches!(&error, Error::CorruptFile { .. }));
488 assert!(
489 error
490 .to_string()
491 .contains("total length exceeding u64::MAX"),
492 "got {error}"
493 );
494 }
495
496 #[test]
497 fn test_write_read_row_ids() {
498 let mut sequence = RowIdSequence::from(0..20);
499 sequence.0.push(U64Segment::Range(30..100));
500 sequence.0.push(U64Segment::RangeWithHoles {
501 range: 100..200,
502 holes: EncodedU64Array::U64(vec![104, 108, 150]),
503 });
504 let mut bitmap = Bitmap::new_empty(100);
505 bitmap.set(99);
506 sequence.0.push(U64Segment::RangeWithBitmap {
507 range: 200..300,
508 bitmap,
509 });
510 sequence
511 .0
512 .push(U64Segment::SortedArray(EncodedU64Array::U16 {
513 base: 200,
514 offsets: vec![1, 2, 3],
515 }));
516 sequence
517 .0
518 .push(U64Segment::Array(EncodedU64Array::U64(vec![3, 1, 2])));
519
520 let serialized = write_row_ids(&sequence);
521
522 let sequence2 = read_row_ids(&serialized).unwrap();
523
524 assert!(matches!(sequence2.0[0], U64Segment::Ranges { .. }));
527 assert_eq!(&sequence2.0[1..], &sequence.0[2..]);
528 assert_eq!(
529 sequence2.iter().collect::<Vec<_>>(),
530 sequence.iter().collect::<Vec<_>>()
531 );
532 }
533
534 proptest! {
535 #[test]
536 fn test_row_id_sequence_len_round_trips(
537 values in proptest::collection::btree_set(any::<u64>(), 0..128)
538 ) {
539 let values = values.into_iter().collect::<Vec<_>>();
540 let sequence = RowIdSequence::from(values.as_slice());
541 let deserialized = read_row_ids(&write_row_ids(&sequence)).unwrap();
542
543 prop_assert_eq!(deserialized.len(), sequence.len());
544 prop_assert_eq!(deserialized.iter().collect::<Vec<_>>(), values);
545 }
546
547 #[test]
548 fn test_rejects_wrong_range_with_bitmap_length(
549 range_len in 1usize..512,
550 ) {
551 let expected_len = range_len.div_ceil(8);
552 for actual_len in [expected_len - 1, expected_len + 1] {
553 let segment = pb::u64_segment::Segment::RangeWithBitmap(
554 pb::u64_segment::RangeWithBitmap {
555 start: 0,
556 end: range_len as u64,
557 bitmap: vec![0; actual_len],
558 },
559 );
560
561 let error = read_segment(segment).unwrap_err();
562 let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
563 prop_assert!(is_corrupt_file);
564 prop_assert!(error.to_string().contains("byte length"));
565 }
566 }
567
568 #[test]
569 fn test_rejects_range_with_bitmap_padding_bits(
570 full_bytes in 0usize..64,
571 valid_bits in 1usize..8,
572 ) {
573 let range_len = full_bytes * 8 + valid_bits;
574 let mut bitmap = vec![0; full_bytes + 1];
575 bitmap[full_bytes] = 1 << valid_bits;
576 let segment = pb::u64_segment::Segment::RangeWithBitmap(
577 pb::u64_segment::RangeWithBitmap {
578 start: 0,
579 end: range_len as u64,
580 bitmap,
581 },
582 );
583
584 let error = read_segment(segment).unwrap_err();
585 let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
586 prop_assert!(is_corrupt_file);
587 prop_assert!(error.to_string().contains("padding bits must be zero"));
588 }
589
590 #[test]
591 fn test_rejects_reversed_range_with_bitmap(
592 start in 1u64..u64::MAX,
593 ) {
594 let segment = pb::u64_segment::Segment::RangeWithBitmap(
595 pb::u64_segment::RangeWithBitmap {
596 start,
597 end: start - 1,
598 bitmap: Vec::new(),
599 },
600 );
601
602 let error = read_segment(segment).unwrap_err();
603 let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
604 prop_assert!(is_corrupt_file);
605 prop_assert!(error.to_string().contains("range start"));
606 }
607
608 #[test]
609 fn test_rejects_misaligned_encoded_array_bytes(
610 encoding in 0u8..3,
611 element_count in 0usize..16,
612 ) {
613 let width = match encoding {
614 0 => 2,
615 1 => 4,
616 _ => 8,
617 };
618 let bytes = vec![0; element_count * width + 1];
619 let array = match encoding {
620 0 => pb::encoded_u64_array::Array::U16Array(
621 pb::encoded_u64_array::U16Array { base: 0, offsets: bytes },
622 ),
623 1 => pb::encoded_u64_array::Array::U32Array(
624 pb::encoded_u64_array::U32Array { base: 0, offsets: bytes },
625 ),
626 _ => pb::encoded_u64_array::Array::U64Array(
627 pb::encoded_u64_array::U64Array { values: bytes },
628 ),
629 };
630 let segment = pb::u64_segment::Segment::Array(pb::EncodedU64Array {
631 array: Some(array),
632 });
633
634 let error = read_segment(segment).unwrap_err();
635 let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
636 prop_assert!(is_corrupt_file);
637 prop_assert!(error.to_string().contains("byte length"));
638 }
639 }
640
641 #[test]
642 fn test_rejects_encoded_offset_overflow() {
643 use pb::encoded_u64_array as pb_array;
644
645 let arrays = [
646 pb_array::Array::U16Array(pb_array::U16Array {
647 base: u64::MAX,
648 offsets: 1u16.to_le_bytes().to_vec(),
649 }),
650 pb_array::Array::U32Array(pb_array::U32Array {
651 base: u64::MAX,
652 offsets: 1u32.to_le_bytes().to_vec(),
653 }),
654 ];
655 for array in arrays {
656 assert_corrupt_segment(
657 pb::u64_segment::Segment::Array(pb::EncodedU64Array { array: Some(array) }),
658 "overflows u64",
659 );
660 }
661 }
662
663 #[rstest]
664 #[case::descending(vec![6, 5], "not strictly increasing")]
665 #[case::duplicate(vec![5, 5], "not strictly increasing")]
666 #[case::below_range(vec![4], "outside the range")]
667 #[case::at_end(vec![7], "outside the range")]
668 fn test_rejects_invalid_range_with_holes(#[case] values: Vec<u64>, #[case] message: &str) {
669 let values = values.into_iter().flat_map(u64::to_le_bytes).collect();
670 assert_corrupt_segment(
671 pb::u64_segment::Segment::RangeWithHoles(pb::u64_segment::RangeWithHoles {
672 start: 5,
673 end: 7,
674 holes: Some(pb::EncodedU64Array {
675 array: Some(pb::encoded_u64_array::Array::U64Array(
676 pb::encoded_u64_array::U64Array { values },
677 )),
678 }),
679 }),
680 message,
681 );
682 }
683
684 #[test]
685 fn test_rejects_missing_range_with_holes_array() {
686 assert_corrupt_segment(
687 pb::u64_segment::Segment::RangeWithHoles(pb::u64_segment::RangeWithHoles {
688 start: 5,
689 end: 7,
690 holes: None,
691 }),
692 "missing its holes array",
693 );
694 }
695
696 #[rstest]
697 #[case::u16(
698 pb::encoded_u64_array::Array::U16Array(pb::encoded_u64_array::U16Array {
699 base: 0,
700 offsets: vec![1],
701 }),
702 "encoded u16 array byte length 1 is not a multiple of element width 2"
703 )]
704 #[case::u32(
705 pb::encoded_u64_array::Array::U32Array(pb::encoded_u64_array::U32Array {
706 base: 0,
707 offsets: vec![1, 2, 3],
708 }),
709 "encoded u32 array byte length 3 is not a multiple of element width 4"
710 )]
711 #[case::u64(
712 pb::encoded_u64_array::Array::U64Array(pb::encoded_u64_array::U64Array {
713 values: vec![1, 2, 3, 4, 5, 6, 7],
714 }),
715 "encoded u64 array byte length 7 is not a multiple of element width 8"
716 )]
717 fn test_rejects_misaligned_encoded_array(
718 #[case] array: pb::encoded_u64_array::Array,
719 #[case] message: &str,
720 ) {
721 assert_corrupt_segment(
722 pb::u64_segment::Segment::Array(pb::EncodedU64Array { array: Some(array) }),
723 message,
724 );
725 }
726
727 #[rstest]
728 #[case::range(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
729 start: 10,
730 end: 9,
731 }))]
732 #[case::range_with_holes(pb::u64_segment::Segment::RangeWithHoles(
733 pb::u64_segment::RangeWithHoles {
734 start: 10,
735 end: 9,
736 holes: Some(pb::EncodedU64Array {
737 array: Some(pb::encoded_u64_array::Array::U64Array(
738 pb::encoded_u64_array::U64Array { values: Vec::new() },
739 )),
740 }),
741 }
742 ))]
743 #[case::range_with_bitmap(pb::u64_segment::Segment::RangeWithBitmap(
744 pb::u64_segment::RangeWithBitmap {
745 start: 10,
746 end: 9,
747 bitmap: Vec::new(),
748 }
749 ))]
750 fn test_rejects_reversed_range(#[case] segment: pb::u64_segment::Segment) {
751 assert_corrupt_segment(segment, "range start 10 exceeds end 9");
752 }
753
754 #[rstest]
755 #[case::short(vec![0])]
756 #[case::long(vec![0, 0, 0])]
757 fn test_rejects_incorrect_bitmap_length(#[case] bitmap: Vec<u8>) {
758 assert_corrupt_segment(
759 pb::u64_segment::Segment::RangeWithBitmap(pb::u64_segment::RangeWithBitmap {
760 start: 5,
761 end: 14,
762 bitmap,
763 }),
764 "does not match expected 2 for range start 5, end 14, and length 9",
765 );
766 }
767
768 #[test]
769 fn test_rejects_set_bitmap_padding_bits() {
770 assert_corrupt_segment(
771 pb::u64_segment::Segment::RangeWithBitmap(pb::u64_segment::RangeWithBitmap {
772 start: 5,
773 end: 14,
774 bitmap: vec![0xff, 0x03],
775 }),
776 "padding bits must be zero",
777 );
778 }
779
780 #[rstest]
781 #[case::u16_descending(pb::encoded_u64_array::Array::U16Array(
782 pb::encoded_u64_array::U16Array {
783 base: 100,
784 offsets: vec![2, 0, 1, 0],
785 }
786 ))]
787 #[case::u32_descending(pb::encoded_u64_array::Array::U32Array(
788 pb::encoded_u64_array::U32Array {
789 base: 100,
790 offsets: vec![2, 0, 0, 0, 1, 0, 0, 0],
791 }
792 ))]
793 #[case::u64_descending(pb::encoded_u64_array::Array::U64Array(
794 pb::encoded_u64_array::U64Array {
795 values: vec![2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
796 }
797 ))]
798 #[case::u16_duplicate(pb::encoded_u64_array::Array::U16Array(
799 pb::encoded_u64_array::U16Array {
800 base: 100,
801 offsets: vec![5, 0, 5, 0],
802 }
803 ))]
804 #[case::u32_duplicate(pb::encoded_u64_array::Array::U32Array(
805 pb::encoded_u64_array::U32Array {
806 base: 100,
807 offsets: vec![5, 0, 0, 0, 5, 0, 0, 0],
808 }
809 ))]
810 #[case::u64_duplicate(pb::encoded_u64_array::Array::U64Array(
811 pb::encoded_u64_array::U64Array {
812 values: vec![5, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0],
813 }
814 ))]
815 fn test_rejects_unsorted_sorted_array(#[case] array: pb::encoded_u64_array::Array) {
816 assert_corrupt_segment(
817 pb::u64_segment::Segment::SortedArray(pb::EncodedU64Array { array: Some(array) }),
818 "SortedArray values are not strictly increasing at indices 0 and 1",
819 );
820 }
821
822 #[test]
823 fn test_ranges_are_written_as_range_segments_and_folded_back() {
824 let ranges = vec![1000..1100, 1300..1500, 1650..2000];
826 let sequence = RowIdSequence(vec![U64Segment::from_sorted_ranges(&ranges).unwrap()]);
827 let encoded = write_row_ids(&sequence);
828
829 let wire = pb::RowIdSequence::decode(encoded.as_slice()).unwrap();
831 let wire_ranges: Vec<Range<u64>> = wire
832 .segments
833 .iter()
834 .map(|segment| match &segment.segment {
835 Some(pb::u64_segment::Segment::Range(range)) => range.start..range.end,
836 other => panic!("expected a Range segment, got {other:?}"),
837 })
838 .collect();
839 assert_eq!(wire_ranges, ranges);
840
841 let decoded = read_row_ids(encoded.as_slice()).unwrap();
843 assert_eq!(decoded, sequence);
844 assert_eq!(
845 decoded.iter().collect::<Vec<_>>(),
846 sequence.iter().collect::<Vec<_>>()
847 );
848
849 let mixed = pb::RowIdSequence {
852 segments: vec![
853 U64Segment::Range(0..10),
854 U64Segment::Range(10..20),
855 U64Segment::SortedArray(vec![100, 200].into()),
856 U64Segment::Range(300..310),
857 U64Segment::Range(320..330),
858 U64Segment::Range(340..350),
859 ]
860 .into_iter()
861 .map(pb::U64Segment::from)
862 .collect(),
863 };
864 let folded = RowIdSequence::try_from(mixed).unwrap();
865 assert!(matches!(
866 folded.0.as_slice(),
867 [
868 U64Segment::Range(_),
869 U64Segment::SortedArray(_),
870 U64Segment::Ranges { .. }
871 ]
872 ));
873 assert_eq!(folded.0[0], U64Segment::Range(0..20));
874 assert_eq!(
875 folded.iter().collect::<Vec<_>>(),
876 (0..20)
877 .chain([100, 200])
878 .chain(300..310)
879 .chain(320..330)
880 .chain(340..350)
881 .collect::<Vec<_>>()
882 );
883 }
884}