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