1use crate::wire::manifest::MetadataRow;
39use serde::{Deserialize, Serialize};
40use std::io::Read;
41use std::num::NonZeroUsize;
42use thiserror::Error;
43use xxhash_rust::xxh64::xxh64;
44
45pub const DEFAULT_TARGET_BLOCK_BYTES: usize = 64 * 1024;
52pub const DEFAULT_MAX_L0_RUNS: usize = 8;
54pub const DEFAULT_MAX_ROWS_PER_SEGMENT: usize = 65_536;
56pub const DEFAULT_MAX_REORGANIZATION_INPUT_RUNS: usize = 8;
58pub const DEFAULT_MAX_REORGANIZATION_INPUT_ROWS: usize = 131_072;
60pub const DEFAULT_MAX_REORGANIZATION_INPUT_BYTES: usize = 64 * 1024 * 1024;
62pub const DEFAULT_INLINE_FILTER_MAX_BYTES: u32 = 1024;
64pub const RESTART_INTERVAL: usize = 16;
66pub const FILTER_BITS_PER_KEY: usize = 10;
68pub const FILTER_HASH_COUNT: u32 = 7;
70
71const FILTER_HASH_SEED_ONE: u64 = 0;
72const FILTER_HASH_SEED_TWO: u64 = 0x9e37_79b9_7f4a_7c15;
73pub(crate) const ZSTD_LEVEL: i32 = 3;
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81pub struct BlockHandle {
82 pub offset: u64,
84 pub stored_len: u32,
86 pub decoded_len: u32,
88 pub crc32c: u32,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct SegmentIndexEntry {
95 pub last_key: String,
97 pub block: BlockHandle,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct BuiltSegmentBlocks {
105 pub bytes: Vec<u8>,
107 pub index: BlockHandle,
109 pub filter: BlockHandle,
111 pub row_count: u64,
113 pub min_key: String,
115 pub max_key: String,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct DecodedDataBlock<R = MetadataRow> {
124 pub row_keys: Vec<String>,
126 pub rows: Vec<R>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct SegmentFilter {
133 n_hashes: u32,
134 bit_len: u64,
135 bits: Vec<u8>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Error)]
142#[non_exhaustive]
143pub enum SstBlockCodecError {
144 #[error("segment must contain at least one row")]
146 EmptySegment,
147 #[error("row key `{offered}` is not in ascending order after `{previous}`")]
149 RowKeysOutOfOrder {
150 previous: String,
152 offered: String,
154 },
155 #[error("stored bytes length {actual} does not match handle length {expected}")]
157 StoredLengthMismatch {
158 expected: u32,
160 actual: usize,
162 },
163 #[error("block checksum mismatch: expected {expected:#010x}, actual {actual:#010x}")]
165 ChecksumMismatch {
166 expected: u32,
168 actual: u32,
170 },
171 #[error("decoded length {actual} does not match handle length {expected}")]
173 DecodedLengthMismatch {
174 expected: u32,
176 actual: usize,
178 },
179 #[error("malformed block: {0}")]
181 Malformed(String),
182 #[error("block codec error: {0}")]
184 Codec(String),
185}
186
187#[derive(Debug)]
189#[must_use]
190pub struct SegmentBlocksBuilder {
191 target_block_bytes: usize,
192 entries: Vec<u8>,
193 restarts: Vec<u32>,
194 entry_count: usize,
195 previous_key: String,
200 finished_blocks: Vec<(String, Vec<u8>)>,
201 filter_hashes: Vec<(u64, u64)>,
202 row_count: u64,
203 min_key: String,
204}
205
206impl Default for SegmentBlocksBuilder {
207 fn default() -> Self {
208 Self::new(const { NonZeroUsize::new(DEFAULT_TARGET_BLOCK_BYTES).unwrap() })
209 }
210}
211
212impl SegmentBlocksBuilder {
213 pub fn new(target_block_bytes: NonZeroUsize) -> Self {
215 Self {
216 target_block_bytes: target_block_bytes.get(),
217 entries: Vec::new(),
218 restarts: Vec::new(),
219 entry_count: 0,
220 previous_key: String::new(),
221 finished_blocks: Vec::new(),
222 filter_hashes: Vec::new(),
223 row_count: 0,
224 min_key: String::new(),
225 }
226 }
227
228 pub fn push<R: Serialize>(
233 &mut self,
234 row_key: &str,
235 filter_key: &str,
236 row: &R,
237 ) -> Result<(), SstBlockCodecError> {
238 if self.row_count > 0 && row_key < self.previous_key.as_str() {
239 return Err(SstBlockCodecError::RowKeysOutOfOrder {
240 previous: self.previous_key.clone(),
241 offered: row_key.to_owned(),
242 });
243 }
244 if self.row_count == 0 {
245 self.min_key = row_key.to_owned();
246 }
247 self.filter_hashes.push(filter_key_hashes(filter_key));
248
249 let restart = self.entry_count % RESTART_INTERVAL == 0;
250 if restart {
251 self.restarts.push(self.entries.len() as u32);
252 }
253 let shared_len = if restart {
254 0
255 } else {
256 shared_prefix_len(&self.previous_key, row_key)
257 };
258 let suffix = &row_key.as_bytes()[shared_len..];
259 let mut row_bytes = Vec::new();
260 ciborium::ser::into_writer(row, &mut row_bytes)
261 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?;
262 write_varint(&mut self.entries, shared_len as u64);
263 write_varint(&mut self.entries, suffix.len() as u64);
264 self.entries.extend_from_slice(suffix);
265 write_varint(&mut self.entries, row_bytes.len() as u64);
266 self.entries.extend_from_slice(&row_bytes);
267
268 self.entry_count += 1;
269 self.row_count += 1;
270 self.previous_key.clear();
271 self.previous_key.push_str(row_key);
272 if self.entries.len() >= self.target_block_bytes {
273 self.finish_data_block();
274 }
275 Ok(())
276 }
277
278 fn finish_data_block(&mut self) {
279 if self.entries.is_empty() {
280 return;
281 }
282 let mut payload = std::mem::take(&mut self.entries);
283 for restart in &self.restarts {
284 payload.extend_from_slice(&restart.to_le_bytes());
285 }
286 payload.extend_from_slice(&(self.restarts.len() as u32).to_le_bytes());
287 self.restarts.clear();
288 self.entry_count = 0;
289 self.finished_blocks
294 .push((self.previous_key.clone(), payload));
295 }
296
297 pub fn finish(mut self) -> Result<BuiltSegmentBlocks, SstBlockCodecError> {
299 if self.row_count == 0 {
300 return Err(SstBlockCodecError::EmptySegment);
301 }
302 self.finish_data_block();
303
304 let mut bytes = Vec::new();
305 let mut index = Vec::with_capacity(self.finished_blocks.len());
306 for (last_key, payload) in std::mem::take(&mut self.finished_blocks) {
307 let block = append_section(&mut bytes, &payload, true)?;
308 index.push(SegmentIndexEntry { last_key, block });
309 }
310
311 let filter_payload = build_filter_payload(&self.filter_hashes);
312 let filter = append_section(&mut bytes, &filter_payload, false)?;
313
314 let mut index_payload = Vec::new();
315 ciborium::ser::into_writer(&index, &mut index_payload)
316 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?;
317 let index = append_section(&mut bytes, &index_payload, true)?;
318
319 Ok(BuiltSegmentBlocks {
320 bytes,
321 index,
322 filter,
323 row_count: self.row_count,
324 min_key: self.min_key,
325 max_key: self.previous_key,
326 })
327 }
328}
329
330pub fn decode_index_block(
332 stored: &[u8],
333 handle: &BlockHandle,
334) -> Result<Vec<SegmentIndexEntry>, SstBlockCodecError> {
335 let payload = decode_section(stored, handle, true)?;
336 let entries: Vec<SegmentIndexEntry> = ciborium::de::from_reader(payload.as_slice())
337 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?;
338 let mut previous: Option<(&String, u64)> = None;
344 for entry in &entries {
345 let end = entry
346 .block
347 .offset
348 .checked_add(u64::from(entry.block.stored_len))
349 .ok_or_else(|| {
350 SstBlockCodecError::Malformed(format!(
351 "index block `{}` byte range overflows",
352 entry.last_key
353 ))
354 })?;
355 if let Some((previous_key, previous_end)) = previous {
356 if previous_key > &entry.last_key {
357 return Err(SstBlockCodecError::Malformed(format!(
358 "index blocks out of key order: `{}` follows `{previous_key}`",
359 entry.last_key
360 )));
361 }
362 if entry.block.offset != previous_end {
363 return Err(SstBlockCodecError::Malformed(format!(
364 "index block `{}` does not start where `{previous_key}` ends",
365 entry.last_key
366 )));
367 }
368 }
369 previous = Some((&entry.last_key, end));
370 }
371 Ok(entries)
372}
373
374pub fn decode_data_block(
376 stored: &[u8],
377 handle: &BlockHandle,
378) -> Result<DecodedDataBlock, SstBlockCodecError> {
379 decode_data_block_rows::<MetadataRow>(stored, handle)
380}
381
382pub fn decode_data_block_rows<R: serde::de::DeserializeOwned>(
385 stored: &[u8],
386 handle: &BlockHandle,
387) -> Result<DecodedDataBlock<R>, SstBlockCodecError> {
388 let payload = decode_section(stored, handle, true)?;
389 if payload.len() < 4 {
390 return Err(SstBlockCodecError::Malformed(
391 "data block shorter than its restart count".to_owned(),
392 ));
393 }
394 let (body, restart_count_bytes) = payload.split_at(payload.len() - 4);
395 let restart_count = u32::from_le_bytes(
396 restart_count_bytes
397 .try_into()
398 .expect("split_at should leave exactly four bytes"),
399 ) as usize;
400 let restarts_len = restart_count
401 .checked_mul(4)
402 .filter(|len| *len <= body.len())
403 .ok_or_else(|| SstBlockCodecError::Malformed("restart array exceeds block".to_owned()))?;
404 let entries = &body[..body.len() - restarts_len];
405
406 let mut row_keys = Vec::new();
407 let mut rows = Vec::new();
408 let mut cursor = 0usize;
409 let mut previous_key = String::new();
410 while cursor < entries.len() {
411 let shared_len = read_varint(entries, &mut cursor)? as usize;
412 let suffix_len = read_varint(entries, &mut cursor)? as usize;
413 if shared_len > previous_key.len() || !previous_key.is_char_boundary(shared_len) {
414 return Err(SstBlockCodecError::Malformed(
415 "shared prefix exceeds previous key".to_owned(),
416 ));
417 }
418 let suffix = take_slice(entries, &mut cursor, suffix_len)?;
419 let suffix = std::str::from_utf8(suffix)
420 .map_err(|_| SstBlockCodecError::Malformed("row key is not utf-8".to_owned()))?;
421 let mut key = String::with_capacity(shared_len + suffix.len());
422 key.push_str(&previous_key[..shared_len]);
423 key.push_str(suffix);
424 let row_len = read_varint(entries, &mut cursor)? as usize;
425 let row_bytes = take_slice(entries, &mut cursor, row_len)?;
426 let row: R = ciborium::de::from_reader(row_bytes)
427 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?;
428 if key.as_str() < previous_key.as_str() {
431 return Err(SstBlockCodecError::Malformed(format!(
432 "rows out of row-key order: `{key}` follows `{previous_key}`"
433 )));
434 }
435 previous_key.clear();
436 previous_key.push_str(&key);
437 row_keys.push(key);
438 rows.push(row);
439 }
440 Ok(DecodedDataBlock { row_keys, rows })
441}
442
443pub fn decode_filter_block(
445 stored: &[u8],
446 handle: &BlockHandle,
447) -> Result<SegmentFilter, SstBlockCodecError> {
448 let payload = decode_section(stored, handle, false)?;
449 if payload.len() < 12 {
450 return Err(SstBlockCodecError::Malformed(
451 "filter block shorter than its header".to_owned(),
452 ));
453 }
454 let n_hashes = u32::from_le_bytes(
455 payload[0..4]
456 .try_into()
457 .expect("header length should be checked above"),
458 );
459 let bit_len = u64::from_le_bytes(
460 payload[4..12]
461 .try_into()
462 .expect("header length should be checked above"),
463 );
464 let bits = payload[12..].to_vec();
465 if bit_len.div_ceil(8) != bits.len() as u64 {
466 return Err(SstBlockCodecError::Malformed(
467 "filter bit length disagrees with its bytes".to_owned(),
468 ));
469 }
470 Ok(SegmentFilter {
471 n_hashes,
472 bit_len,
473 bits,
474 })
475}
476
477impl SegmentFilter {
478 pub fn may_contain(&self, filter_key: &str) -> bool {
481 if self.bit_len == 0 {
482 return false;
483 }
484 let (h1, h2) = filter_key_hashes(filter_key);
485 for probe in 0..u64::from(self.n_hashes) {
486 let bit = h1.wrapping_add(probe.wrapping_mul(h2)) % self.bit_len;
487 let byte = self.bits[(bit / 8) as usize];
488 if byte & (1 << (bit % 8)) == 0 {
489 return false;
490 }
491 }
492 true
493 }
494}
495
496pub fn string_prefix_upper_bound(prefix: &str) -> Option<String> {
502 let mut bytes = prefix.as_bytes().to_vec();
503 for index in (0..bytes.len()).rev() {
504 if bytes[index] != u8::MAX {
505 bytes[index] += 1;
506 bytes.truncate(index + 1);
507 return String::from_utf8(bytes).ok();
508 }
509 }
510 None
511}
512
513pub fn index_blocks_for_key_range(
516 index: &[SegmentIndexEntry],
517 lower_bound: &str,
518 upper_bound: Option<&str>,
519) -> std::ops::Range<usize> {
520 let start = index.partition_point(|entry| entry.last_key.as_str() < lower_bound);
521 let end = upper_bound.map_or(index.len(), |upper_bound| {
522 index
525 .partition_point(|entry| entry.last_key.as_str() < upper_bound)
526 .saturating_add(1)
527 .min(index.len())
528 });
529 start..end.max(start)
530}
531
532fn append_section(
533 bytes: &mut Vec<u8>,
534 payload: &[u8],
535 compress: bool,
536) -> Result<BlockHandle, SstBlockCodecError> {
537 let stored = if compress {
538 zstd::bulk::compress(payload, ZSTD_LEVEL)
539 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?
540 } else {
541 payload.to_vec()
542 };
543 let handle = BlockHandle {
544 offset: bytes.len() as u64,
545 stored_len: stored.len() as u32,
546 decoded_len: payload.len() as u32,
547 crc32c: crc32c::crc32c(&stored),
548 };
549 bytes.extend_from_slice(&stored);
550 Ok(handle)
551}
552
553fn decode_section(
554 stored: &[u8],
555 handle: &BlockHandle,
556 compressed: bool,
557) -> Result<Vec<u8>, SstBlockCodecError> {
558 if stored.len() != handle.stored_len as usize {
559 return Err(SstBlockCodecError::StoredLengthMismatch {
560 expected: handle.stored_len,
561 actual: stored.len(),
562 });
563 }
564 let actual = crc32c::crc32c(stored);
565 if actual != handle.crc32c {
566 return Err(SstBlockCodecError::ChecksumMismatch {
567 expected: handle.crc32c,
568 actual,
569 });
570 }
571 let payload = if compressed {
572 let mut payload = Vec::with_capacity(handle.decoded_len as usize);
573 zstd::Decoder::new(stored)
574 .and_then(|mut decoder| decoder.read_to_end(&mut payload))
575 .map_err(|error| SstBlockCodecError::Codec(error.to_string()))?;
576 payload
577 } else {
578 stored.to_vec()
579 };
580 if payload.len() != handle.decoded_len as usize {
581 return Err(SstBlockCodecError::DecodedLengthMismatch {
582 expected: handle.decoded_len,
583 actual: payload.len(),
584 });
585 }
586 Ok(payload)
587}
588
589fn build_filter_payload(hashes: &[(u64, u64)]) -> Vec<u8> {
590 let bit_len = (hashes.len() * FILTER_BITS_PER_KEY).max(64) as u64;
591 let mut bits = vec![0u8; bit_len.div_ceil(8) as usize];
592 for (h1, h2) in hashes {
593 for probe in 0..u64::from(FILTER_HASH_COUNT) {
594 let bit = h1.wrapping_add(probe.wrapping_mul(*h2)) % bit_len;
595 bits[(bit / 8) as usize] |= 1 << (bit % 8);
596 }
597 }
598 let mut payload = Vec::with_capacity(12 + bits.len());
599 payload.extend_from_slice(&FILTER_HASH_COUNT.to_le_bytes());
600 payload.extend_from_slice(&bit_len.to_le_bytes());
601 payload.extend_from_slice(&bits);
602 payload
603}
604
605fn filter_key_hashes(filter_key: &str) -> (u64, u64) {
606 (
607 xxh64(filter_key.as_bytes(), FILTER_HASH_SEED_ONE),
608 xxh64(filter_key.as_bytes(), FILTER_HASH_SEED_TWO),
609 )
610}
611
612fn shared_prefix_len(previous: &str, current: &str) -> usize {
613 let mut len = previous
614 .as_bytes()
615 .iter()
616 .zip(current.as_bytes())
617 .take_while(|(a, b)| a == b)
618 .count();
619 while !current.is_char_boundary(len) {
622 len -= 1;
623 }
624 len
625}
626
627pub(crate) fn write_varint(bytes: &mut Vec<u8>, mut value: u64) {
628 loop {
629 let byte = (value & 0x7f) as u8;
630 value >>= 7;
631 if value == 0 {
632 bytes.push(byte);
633 return;
634 }
635 bytes.push(byte | 0x80);
636 }
637}
638
639pub(crate) fn read_varint(bytes: &[u8], cursor: &mut usize) -> Result<u64, SstBlockCodecError> {
640 let mut value = 0u64;
641 let mut shift = 0u32;
642 loop {
643 let byte = *bytes.get(*cursor).ok_or_else(|| {
644 SstBlockCodecError::Malformed("varint runs past the block".to_owned())
645 })?;
646 *cursor += 1;
647 if shift >= 64 {
648 return Err(SstBlockCodecError::Malformed(
649 "varint exceeds 64 bits".to_owned(),
650 ));
651 }
652 value |= u64::from(byte & 0x7f) << shift;
653 if byte & 0x80 == 0 {
654 return Ok(value);
655 }
656 shift += 7;
657 }
658}
659
660fn take_slice<'a>(
661 bytes: &'a [u8],
662 cursor: &mut usize,
663 len: usize,
664) -> Result<&'a [u8], SstBlockCodecError> {
665 let end = cursor.checked_add(len).filter(|end| *end <= bytes.len());
666 match end {
667 Some(end) => {
668 let slice = &bytes[*cursor..end];
669 *cursor = end;
670 Ok(slice)
671 }
672 None => Err(SstBlockCodecError::Malformed(
673 "entry runs past the block".to_owned(),
674 )),
675 }
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681 use crate::{ChangeSeq, InodeId, InodeKind};
682
683 fn inode_row(inode_id: u64) -> (String, String, MetadataRow) {
684 let row = MetadataRow::Inode {
685 inode_id: InodeId(inode_id),
686 inode_kind: InodeKind::File,
687 created_seq: ChangeSeq(inode_id),
688 created_by: crate::ActorRef::loonfs_system(),
689 created_at_ms: inode_id,
690 };
691 let key = row.row_key();
692 (key.clone(), key, row)
693 }
694
695 fn build_segment(rows: usize) -> BuiltSegmentBlocks {
696 let mut builder = SegmentBlocksBuilder::default();
697 for index in 0..rows {
698 let (key, filter_key, row) = inode_row(index as u64);
699 builder.push(&key, &filter_key, &row).expect("push row");
700 }
701 builder.finish().expect("finish segment")
702 }
703
704 fn section<'a>(bytes: &'a [u8], handle: &BlockHandle) -> &'a [u8] {
705 &bytes[handle.offset as usize..handle.offset as usize + handle.stored_len as usize]
706 }
707
708 fn encode_index(entries: &[SegmentIndexEntry]) -> (Vec<u8>, BlockHandle) {
709 let mut payload = Vec::new();
710 ciborium::ser::into_writer(entries, &mut payload).expect("encode index");
711 let mut bytes = Vec::new();
712 let handle = append_section(&mut bytes, &payload, true).expect("append section");
713 (bytes, handle)
714 }
715
716 fn index_entry(last_key: &str, offset: u64, stored_len: u32) -> SegmentIndexEntry {
717 SegmentIndexEntry {
718 last_key: last_key.to_owned(),
719 block: BlockHandle {
720 offset,
721 stored_len,
722 decoded_len: stored_len,
723 crc32c: 0,
724 },
725 }
726 }
727
728 #[test]
729 fn segment_round_trips_every_row_through_index_and_blocks() {
730 let rows = 5_000;
731 let built = build_segment(rows);
732 let index =
733 decode_index_block(section(&built.bytes, &built.index), &built.index).expect("index");
734 assert!(index.len() > 1, "5k inode rows should span several blocks");
735
736 let mut recovered = Vec::new();
737 for entry in &index {
738 let block = decode_data_block(section(&built.bytes, &entry.block), &entry.block)
739 .expect("data block");
740 assert_eq!(block.row_keys.len(), block.rows.len());
741 assert_eq!(
742 block.row_keys.last().expect("blocks are never empty"),
743 &entry.last_key
744 );
745 recovered.extend(block.row_keys.iter().cloned());
746 }
747 let expected: Vec<String> = (0..rows).map(|i| inode_row(i as u64).0).collect();
748 assert_eq!(recovered, expected);
749 assert_eq!(built.row_count, rows as u64);
750 assert_eq!(built.min_key, expected[0]);
751 assert_eq!(&built.max_key, expected.last().expect("rows"));
752 }
753
754 #[test]
755 fn index_narrows_point_lookups_to_one_block() {
756 let built = build_segment(5_000);
757 let index =
758 decode_index_block(section(&built.bytes, &built.index), &built.index).expect("index");
759 let (key, _, row) = inode_row(3_217);
760 let upper = format!("{key}\0");
761 let range = index_blocks_for_key_range(&index, &key, Some(&upper));
762 assert_eq!(range.len(), 1, "a point lookup should touch one block");
763 let entry = &index[range.start];
764 let block =
765 decode_data_block(section(&built.bytes, &entry.block), &entry.block).expect("block");
766 let position = block
767 .row_keys
768 .binary_search_by(|candidate| candidate.as_str().cmp(key.as_str()))
769 .expect("row should be present");
770 assert_eq!(block.rows[position], row);
771 }
772
773 #[test]
774 fn key_range_scan_covers_exactly_the_matching_blocks() {
775 let built = build_segment(5_000);
776 let index =
777 decode_index_block(section(&built.bytes, &built.index), &built.index).expect("index");
778 let lower = inode_row(1_000).0;
779 let upper = inode_row(1_500).0;
780 let range = index_blocks_for_key_range(&index, &lower, Some(&upper));
781 let mut keys = Vec::new();
782 for entry in &index[range] {
783 let block = decode_data_block(section(&built.bytes, &entry.block), &entry.block)
784 .expect("block");
785 keys.extend(block.row_keys);
786 }
787 let keys: Vec<&String> = keys
788 .iter()
789 .filter(|key| key.as_str() >= lower.as_str() && key.as_str() < upper.as_str())
790 .collect();
791 assert_eq!(keys.len(), 500);
792 }
793
794 #[test]
795 fn out_of_order_and_empty_segments_are_rejected() {
796 let mut builder = SegmentBlocksBuilder::default();
797 let (key_b, filter_b, row_b) = inode_row(2);
798 let (key_a, filter_a, row_a) = inode_row(1);
799 builder.push(&key_b, &filter_b, &row_b).expect("first row");
800 let error = builder
801 .push(&key_a, &filter_a, &row_a)
802 .expect_err("descending key should be rejected");
803 assert!(matches!(
804 error,
805 SstBlockCodecError::RowKeysOutOfOrder { .. }
806 ));
807
808 let error = SegmentBlocksBuilder::default()
809 .finish()
810 .expect_err("empty segment should be rejected");
811 assert!(matches!(error, SstBlockCodecError::EmptySegment));
812 }
813
814 #[test]
820 fn max_key_survives_a_last_row_that_closes_its_block() {
821 let rows = 100usize;
826 let single_block = build_segment(rows);
827 let calibration = decode_index_block(
828 section(&single_block.bytes, &single_block.index),
829 &single_block.index,
830 )
831 .expect("index");
832 assert_eq!(calibration.len(), 1, "the calibration segment is one block");
833 let restarts = rows.div_ceil(RESTART_INTERVAL);
834 let entry_bytes = calibration[0].block.decoded_len as usize - 4 * restarts - 4;
835
836 let mut builder =
837 SegmentBlocksBuilder::new(NonZeroUsize::new(entry_bytes).expect("positive target"));
838 for index in 0..rows {
839 let (key, filter_key, row) = inode_row(index as u64);
840 builder.push(&key, &filter_key, &row).expect("push row");
841 }
842 let built = builder.finish().expect("finish segment");
843
844 let expected_max = inode_row((rows - 1) as u64).0;
845 assert_eq!(built.min_key, inode_row(0).0);
846 assert_eq!(built.max_key, expected_max);
847 assert_eq!(built.row_count, rows as u64);
848 assert_eq!(built.bytes, single_block.bytes);
851 let index =
852 decode_index_block(section(&built.bytes, &built.index), &built.index).expect("index");
853 assert_eq!(index.len(), 1);
854 assert_eq!(index[0].last_key, expected_max);
855 }
856
857 #[test]
860 fn block_geometry_does_not_change_the_segment_key_range() {
861 let rows = 400usize;
862 let expected: Vec<String> = (0..rows).map(|index| inode_row(index as u64).0).collect();
863 for target in [1usize, 64, 257, 1_024, 4_096, 65_536] {
864 let mut builder =
865 SegmentBlocksBuilder::new(NonZeroUsize::new(target).expect("positive target"));
866 for index in 0..rows {
867 let (key, filter_key, row) = inode_row(index as u64);
868 builder.push(&key, &filter_key, &row).expect("push row");
869 }
870 let built = builder.finish().expect("finish segment");
871 assert_eq!(built.min_key, expected[0], "target {target}");
872 assert_eq!(
873 &built.max_key,
874 expected.last().expect("rows"),
875 "target {target}"
876 );
877 assert_eq!(built.row_count, rows as u64, "target {target}");
878
879 let index = decode_index_block(section(&built.bytes, &built.index), &built.index)
880 .expect("index");
881 let mut recovered = Vec::new();
882 for entry in &index {
883 let block = decode_data_block(section(&built.bytes, &entry.block), &entry.block)
884 .expect("data block");
885 assert_eq!(
888 block.row_keys.last().expect("blocks are never empty"),
889 &entry.last_key,
890 "target {target}"
891 );
892 recovered.extend(block.row_keys);
893 }
894 assert_eq!(recovered, expected, "target {target}");
895 assert_eq!(
896 index.last().expect("blocks").last_key,
897 built.max_key,
898 "target {target}"
899 );
900 }
901 }
902
903 #[test]
907 fn a_descending_row_after_a_block_boundary_is_rejected() {
908 let mut builder = SegmentBlocksBuilder::new(NonZeroUsize::MIN);
909 let (key_high, filter_high, row_high) = inode_row(9);
910 builder
911 .push(&key_high, &filter_high, &row_high)
912 .expect("first row");
913 let (key_low, filter_low, row_low) = inode_row(3);
914 let error = builder
915 .push(&key_low, &filter_low, &row_low)
916 .expect_err("a descending key across a block boundary should be rejected");
917 assert!(
918 matches!(
919 &error,
920 SstBlockCodecError::RowKeysOutOfOrder { previous, offered }
921 if previous == &key_high && offered == &key_low
922 ),
923 "unexpected error: {error}"
924 );
925 }
926
927 #[test]
928 fn adjacent_equal_keys_are_permitted() {
929 let mut builder = SegmentBlocksBuilder::default();
930 let (key, filter_key, row) = inode_row(7);
931 builder.push(&key, &filter_key, &row).expect("first copy");
932 builder.push(&key, &filter_key, &row).expect("second copy");
933 let built = builder.finish().expect("finish");
934 assert_eq!(built.row_count, 2);
935 }
936
937 #[test]
938 fn corrupted_sections_fail_their_checksums() {
939 let built = build_segment(200);
940 let index =
941 decode_index_block(section(&built.bytes, &built.index), &built.index).expect("index");
942
943 let mut corrupted = built.bytes.clone();
944 let target = index[0].block.offset as usize + 3;
945 corrupted[target] ^= 0xff;
946 let error = decode_data_block(section(&corrupted, &index[0].block), &index[0].block)
947 .expect_err("corrupted data block should fail");
948 assert!(matches!(error, SstBlockCodecError::ChecksumMismatch { .. }));
949
950 let mut corrupted = built.bytes.clone();
951 let target = built.index.offset as usize + 3;
952 corrupted[target] ^= 0xff;
953 let error = decode_index_block(section(&corrupted, &built.index), &built.index)
954 .expect_err("corrupted index should fail");
955 assert!(matches!(error, SstBlockCodecError::ChecksumMismatch { .. }));
956
957 let mut corrupted = built.bytes.clone();
958 let target = built.filter.offset as usize + 12;
959 corrupted[target] ^= 0xff;
960 let error = decode_filter_block(section(&corrupted, &built.filter), &built.filter)
961 .expect_err("corrupted filter should fail");
962 assert!(matches!(error, SstBlockCodecError::ChecksumMismatch { .. }));
963 }
964
965 #[test]
966 fn filter_has_no_false_negatives_and_few_false_positives() {
967 let rows = 2_000;
968 let built = build_segment(rows);
969 let filter = decode_filter_block(section(&built.bytes, &built.filter), &built.filter)
970 .expect("filter");
971 for index in 0..rows {
972 let (key, _, _) = inode_row(index as u64);
973 assert!(filter.may_contain(&key), "inserted key must stay positive");
974 }
975 let mut false_positives = 0usize;
976 let probes = 10_000usize;
977 for index in 0..probes {
978 let (absent, _, _) = inode_row((rows + 10_000 + index) as u64);
979 if filter.may_contain(&absent) {
980 false_positives += 1;
981 }
982 }
983 let rate = false_positives as f64 / probes as f64;
984 assert!(rate < 0.02, "false positive rate {rate} exceeds 2%");
985 }
986
987 #[test]
988 fn durable_encoding_is_deterministic() {
989 let first = build_segment(300);
990 let second = build_segment(300);
991 assert_eq!(first.bytes, second.bytes);
992 assert_eq!(first.index, second.index);
993 assert_eq!(first.filter, second.filter);
994 }
995
996 #[test]
997 fn decoding_rejects_out_of_order_rows_in_a_block() {
998 let mut entries = Vec::new();
1001 for inode in [9u64, 3u64] {
1002 let (key, _, row) = inode_row(inode);
1003 let mut row_bytes = Vec::new();
1004 ciborium::ser::into_writer(&row, &mut row_bytes).expect("encode row");
1005 write_varint(&mut entries, 0);
1006 write_varint(&mut entries, key.len() as u64);
1007 entries.extend_from_slice(key.as_bytes());
1008 write_varint(&mut entries, row_bytes.len() as u64);
1009 entries.extend_from_slice(&row_bytes);
1010 }
1011 let mut payload = entries;
1012 payload.extend_from_slice(&0u32.to_le_bytes());
1013 payload.extend_from_slice(&0u32.to_le_bytes());
1014 let mut bytes = Vec::new();
1015 let handle = append_section(&mut bytes, &payload, true).expect("append section");
1016
1017 let error =
1018 decode_data_block(&bytes, &handle).expect_err("descending rows should be rejected");
1019 assert!(
1020 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("row-key order")),
1021 "unexpected error: {error}"
1022 );
1023 }
1024
1025 #[test]
1026 fn decoding_rejects_a_shared_prefix_inside_a_utf8_code_point() {
1027 let (_, _, row) = inode_row(1);
1028 let mut row_bytes = Vec::new();
1029 ciborium::ser::into_writer(&row, &mut row_bytes).expect("encode row");
1030 let mut payload = Vec::new();
1031 write_varint(&mut payload, 0);
1032 write_varint(&mut payload, "é".len() as u64);
1033 payload.extend_from_slice("é".as_bytes());
1034 write_varint(&mut payload, row_bytes.len() as u64);
1035 payload.extend_from_slice(&row_bytes);
1036 write_varint(&mut payload, 1);
1037 write_varint(&mut payload, 0);
1038 write_varint(&mut payload, row_bytes.len() as u64);
1039 payload.extend_from_slice(&row_bytes);
1040 payload.extend_from_slice(&0u32.to_le_bytes());
1041
1042 let mut bytes = Vec::new();
1043 let handle = append_section(&mut bytes, &payload, true).expect("append section");
1044 let error = decode_data_block(&bytes, &handle)
1045 .expect_err("a partial utf-8 prefix should be rejected");
1046 assert!(matches!(
1047 &error,
1048 SstBlockCodecError::Malformed(message)
1049 if message == "shared prefix exceeds previous key"
1050 ));
1051 }
1052
1053 #[test]
1054 fn decoding_rejects_out_of_order_index_entries() {
1055 let entries = vec![
1056 index_entry("inode-00000000000000000009", 0, 1),
1057 index_entry("inode-00000000000000000003", 0, 1),
1058 ];
1059 let (bytes, handle) = encode_index(&entries);
1060
1061 let error =
1062 decode_index_block(&bytes, &handle).expect_err("descending index should be rejected");
1063 assert!(
1064 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("key order")),
1065 "unexpected error: {error}"
1066 );
1067 }
1068
1069 #[test]
1070 fn decoding_rejects_out_of_order_index_offsets() {
1071 let entries = [index_entry("a", 10, 1), index_entry("b", 5, 1)];
1072 let (bytes, handle) = encode_index(&entries);
1073
1074 let error = decode_index_block(&bytes, &handle)
1075 .expect_err("descending block offsets should be rejected");
1076 assert!(
1077 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("does not start where")),
1078 "unexpected error: {error}"
1079 );
1080 }
1081
1082 #[test]
1083 fn decoding_rejects_overlapping_index_ranges() {
1084 let entries = [index_entry("a", 10, 5), index_entry("b", 14, 1)];
1085 let (bytes, handle) = encode_index(&entries);
1086
1087 let error = decode_index_block(&bytes, &handle)
1088 .expect_err("overlapping block ranges should be rejected");
1089 assert!(
1090 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("does not start where")),
1091 "unexpected error: {error}"
1092 );
1093 }
1094
1095 #[test]
1096 fn decoding_rejects_a_gap_between_index_blocks() {
1097 let entries = [index_entry("a", 0, 10), index_entry("b", 20, 1)];
1098 let (bytes, handle) = encode_index(&entries);
1099
1100 let error = decode_index_block(&bytes, &handle)
1101 .expect_err("a gap between block ranges should be rejected");
1102 assert!(
1103 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("does not start where")),
1104 "unexpected error: {error}"
1105 );
1106 }
1107
1108 #[test]
1109 fn decoding_rejects_a_single_block_range_past_the_integer_edge() {
1110 let entries = [index_entry("a", u64::MAX - 10, 100)];
1111 let (bytes, handle) = encode_index(&entries);
1112
1113 let error = decode_index_block(&bytes, &handle)
1114 .expect_err("an overflowing single range should be rejected");
1115 assert!(
1116 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("byte range overflows")),
1117 "unexpected error: {error}"
1118 );
1119 }
1120
1121 #[test]
1122 fn decoding_rejects_a_final_block_range_past_the_integer_edge() {
1123 let entries = [
1124 index_entry("a", u64::MAX - 110, 100),
1125 index_entry("b", u64::MAX - 10, 100),
1126 ];
1127 let (bytes, handle) = encode_index(&entries);
1128
1129 let error = decode_index_block(&bytes, &handle)
1130 .expect_err("a trailing overflowing range should be rejected");
1131 assert!(
1132 matches!(&error, SstBlockCodecError::Malformed(message) if message.contains("byte range overflows")),
1133 "unexpected error: {error}"
1134 );
1135 }
1136}