1use std::cmp::Ordering;
33use std::sync::{Arc, RwLock};
34
35use super::page::{Page, PageType, HEADER_SIZE, PAGE_SIZE};
36use super::pager::{Pager, PagerError};
37
38pub const MAX_KEY_SIZE: usize = 1024;
40
41pub const MAX_VALUE_SIZE: usize = value_layout::MAX_VALUE_SIZE;
54
55pub const MIN_FILL_FACTOR: usize = 25;
57
58const LEAF_PREV_OFFSET: usize = HEADER_SIZE;
60
61const LEAF_NEXT_OFFSET: usize = HEADER_SIZE + 4;
63
64const LEAF_SLOT_ARRAY_OFFSET: usize = HEADER_SIZE + 8;
79
80const LEAF_DATA_OFFSET: usize = LEAF_SLOT_ARRAY_OFFSET;
83
84const INTERIOR_DATA_OFFSET: usize = HEADER_SIZE;
86
87#[derive(Debug, Clone)]
89pub enum BTreeError {
90 NotFound,
92 DuplicateKey,
94 KeyTooLarge(usize),
96 ValueTooLarge(usize),
98 Corrupted(String),
100 Pager(String),
102 LockPoisoned(String),
104 ValueLayout(String),
106}
107
108impl std::fmt::Display for BTreeError {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 match self {
111 Self::NotFound => write!(f, "Key not found"),
112 Self::DuplicateKey => write!(f, "Key already exists"),
113 Self::KeyTooLarge(size) => {
114 write!(f, "Key too large: {} bytes (max {})", size, MAX_KEY_SIZE)
115 }
116 Self::ValueTooLarge(size) => write!(
117 f,
118 "Value too large: {} bytes (max {})",
119 size, MAX_VALUE_SIZE
120 ),
121 Self::Corrupted(msg) => write!(f, "B-tree corrupted: {}", msg),
122 Self::Pager(msg) => write!(f, "Pager error: {}", msg),
123 Self::LockPoisoned(msg) => write!(f, "Lock poisoned: {}", msg),
124 Self::ValueLayout(msg) => write!(f, "Value layout: {}", msg),
125 }
126 }
127}
128
129impl From<value_layout::ValueLayoutError> for BTreeError {
130 fn from(e: value_layout::ValueLayoutError) -> Self {
131 match e {
132 value_layout::ValueLayoutError::ValueTooLarge(n) => Self::ValueTooLarge(n),
133 other => Self::ValueLayout(other.to_string()),
134 }
135 }
136}
137
138impl std::error::Error for BTreeError {}
139
140impl From<PagerError> for BTreeError {
141 fn from(e: PagerError) -> Self {
142 Self::Pager(e.to_string())
143 }
144}
145
146impl From<super::page::PageError> for BTreeError {
147 fn from(e: super::page::PageError) -> Self {
148 Self::Corrupted(e.to_string())
149 }
150}
151
152pub type BTreeResult<T> = Result<T, BTreeError>;
154
155pub struct BTreeCursor {
157 leaf_page_id: u32,
159 position: usize,
161 pager: Arc<Pager>,
163 prefetched_next: bool,
167}
168
169impl BTreeCursor {
170 pub fn next(&mut self) -> BTreeResult<Option<(Vec<u8>, Vec<u8>)>> {
172 if self.leaf_page_id == 0 {
173 return Ok(None);
174 }
175
176 let page = self.pager.read_page(self.leaf_page_id)?;
177 if page.page_type()? != PageType::BTreeLeaf {
178 return Err(BTreeError::Corrupted(
179 "cursor reached a non-leaf page".into(),
180 ));
181 }
182 let cell_count = page.cell_count() as usize;
183
184 if !self.prefetched_next && self.position >= cell_count / 2 && cell_count > 0 {
190 let next_leaf = read_next_leaf(&page);
191 if next_leaf != 0 {
192 self.pager.prefetch_hint(next_leaf);
193 }
194 self.prefetched_next = true;
195 }
196
197 if self.position < cell_count {
199 let (key, stored) = read_leaf_cell(&page, self.position)?;
200 let value = value_layout::decode(&self.pager, &stored)?;
201 self.position += 1;
202 return Ok(Some((key, value)));
203 }
204
205 let next_leaf = read_next_leaf(&page);
207 if next_leaf == 0 {
208 self.leaf_page_id = 0;
209 return Ok(None);
210 }
211 let previous_high_key = leaf_high_key(&page)?;
212
213 self.leaf_page_id = next_leaf;
214 self.position = 0;
215 self.prefetched_next = false; let page = self.pager.read_page(self.leaf_page_id)?;
219 if page.page_type()? != PageType::BTreeLeaf {
220 return Err(BTreeError::Corrupted(
221 "leaf chain reached a non-leaf page".into(),
222 ));
223 }
224 if page.cell_count() == 0 {
225 return Ok(None);
226 }
227
228 let (key, stored) = read_leaf_cell(&page, 0)?;
229 if previous_high_key
233 .as_ref()
234 .is_some_and(|previous| key <= *previous)
235 {
236 self.leaf_page_id = 0;
237 return Err(BTreeError::Corrupted(
238 "leaf chain does not advance its key range".into(),
239 ));
240 }
241 let value = value_layout::decode(&self.pager, &stored)?;
242 self.position = 1;
243 Ok(Some((key, value)))
244 }
245
246 pub fn peek(&self) -> BTreeResult<Option<(Vec<u8>, Vec<u8>)>> {
248 if self.leaf_page_id == 0 {
249 return Ok(None);
250 }
251
252 let page = self.pager.read_page(self.leaf_page_id)?;
253 let cell_count = page.cell_count() as usize;
254
255 if self.position >= cell_count {
256 return Ok(None);
257 }
258
259 let (key, stored) = read_leaf_cell(&page, self.position)?;
260 let value = value_layout::decode(&self.pager, &stored)?;
261 Ok(Some((key, value)))
262 }
263}
264
265pub struct BTree {
267 pager: Arc<Pager>,
269 root_page_id: RwLock<u32>,
271 rightmost_leaf: RwLock<Option<(u32, Vec<u8>)>>,
280 writer: parking_lot::ReentrantMutex<()>,
288}
289
290#[path = "btree/impl.rs"]
291mod btree_impl;
292
293#[path = "btree/lehman_yao.rs"]
294pub mod lehman_yao;
295
296#[path = "btree/value_layout.rs"]
297pub mod value_layout;
298enum SearchResult {
301 Found(usize),
302 NotFound(usize),
303}
304
305fn search_leaf(page: &Page, key: &[u8]) -> BTreeResult<SearchResult> {
306 let cell_count = page.cell_count() as usize;
307
308 let mut low = 0;
310 let mut high = cell_count;
311
312 while low < high {
313 let mid = (low + high) / 2;
314 let (cell_key, _) = read_leaf_cell(page, mid)?;
315
316 match cell_key.as_slice().cmp(key) {
317 Ordering::Less => low = mid + 1,
318 Ordering::Greater => high = mid,
319 Ordering::Equal => return Ok(SearchResult::Found(mid)),
320 }
321 }
322
323 Ok(SearchResult::NotFound(low))
324}
325
326fn find_child(page: &Page, key: &[u8]) -> BTreeResult<u32> {
327 let cell_count = page.cell_count() as usize;
328
329 for i in 0..cell_count {
331 let (cell_key, child) = read_interior_cell(page, i)?;
332 if key < cell_key.as_slice() {
333 return Ok(child);
334 }
335 }
336
337 Ok(page.right_child())
339}
340
341fn find_first_child(page: &Page) -> BTreeResult<u32> {
342 if page.cell_count() == 0 {
343 return Ok(page.right_child());
344 }
345 let (_, child) = read_interior_cell(page, 0)?;
346 Ok(child)
347}
348
349fn leaf_min_bytes() -> usize {
350 (PAGE_SIZE - LEAF_DATA_OFFSET) * MIN_FILL_FACTOR / 100
351}
352
353fn interior_min_bytes() -> usize {
354 (PAGE_SIZE - INTERIOR_DATA_OFFSET) * MIN_FILL_FACTOR / 100
355}
356
357fn leaf_entry_size(entry: &(Vec<u8>, Vec<u8>)) -> usize {
361 2 + 4 + entry.0.len() + entry.1.len()
362}
363
364fn leaf_entries_size(entries: &[(Vec<u8>, Vec<u8>)]) -> usize {
365 entries.iter().map(leaf_entry_size).sum()
366}
367
368#[inline]
369fn leaf_slot_offset_for(index: usize) -> usize {
370 LEAF_SLOT_ARRAY_OFFSET + index * 2
371}
372
373#[inline]
374fn leaf_read_slot(page: &Page, index: usize) -> BTreeResult<usize> {
375 let data = page.as_bytes();
376 let slot_pos = leaf_slot_offset_for(index);
377 if slot_pos + 2 > PAGE_SIZE {
378 return Err(BTreeError::Corrupted("slot array overflows page".into()));
379 }
380 Ok(u16::from_le_bytes([data[slot_pos], data[slot_pos + 1]]) as usize)
381}
382
383#[inline]
384fn leaf_write_slot(page: &mut Page, index: usize, cell_offset: u16) -> BTreeResult<()> {
385 let data = page.as_bytes_mut();
386 let slot_pos = leaf_slot_offset_for(index);
387 if slot_pos + 2 > PAGE_SIZE {
388 return Err(BTreeError::Corrupted("slot array overflows page".into()));
389 }
390 data[slot_pos..slot_pos + 2].copy_from_slice(&cell_offset.to_le_bytes());
391 Ok(())
392}
393
394#[inline]
395fn leaf_slots_end(page: &Page) -> usize {
396 LEAF_SLOT_ARRAY_OFFSET + (page.cell_count() as usize) * 2
397}
398
399#[inline]
400fn leaf_cells_start(page: &Page) -> usize {
401 let end = page.free_end() as usize;
402 if end == 0 {
403 PAGE_SIZE
404 } else {
405 end
406 }
407}
408
409#[inline]
410fn leaf_free_bytes(page: &Page) -> usize {
411 let slot_end = leaf_slots_end(page);
412 let cells = leaf_cells_start(page);
413 cells.saturating_sub(slot_end)
414}
415
416fn interior_key_size(key: &[u8]) -> usize {
417 2 + key.len() + 4
418}
419
420fn interior_entries_size(keys: &[Vec<u8>]) -> usize {
421 keys.iter().map(|k| interior_key_size(k)).sum()
422}
423
424fn read_leaf_cell(page: &Page, index: usize) -> BTreeResult<(Vec<u8>, Vec<u8>)> {
430 let cell_count = page.cell_count() as usize;
431 if index >= cell_count {
432 return Err(BTreeError::Corrupted("Cell index out of range".into()));
433 }
434 let offset = leaf_read_slot(page, index)?;
435 let data = page.as_bytes();
436 if offset + 4 > PAGE_SIZE {
437 return Err(BTreeError::Corrupted("cell header out of range".into()));
438 }
439 let key_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
440 let val_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
441 let end = offset + 4 + key_len + val_len;
442 if end > PAGE_SIZE {
443 return Err(BTreeError::Corrupted("cell body out of range".into()));
444 }
445 let key = data[offset + 4..offset + 4 + key_len].to_vec();
446 let value = data[offset + 4 + key_len..end].to_vec();
447 Ok((key, value))
448}
449
450fn read_leaf_entries(page: &Page) -> BTreeResult<Vec<(Vec<u8>, Vec<u8>)>> {
451 let cell_count = page.cell_count() as usize;
452 let mut entries = Vec::with_capacity(cell_count);
453 for i in 0..cell_count {
454 entries.push(read_leaf_cell(page, i)?);
455 }
456 Ok(entries)
457}
458
459fn write_leaf_entries(page: &mut Page, entries: &[(Vec<u8>, Vec<u8>)]) -> BTreeResult<()> {
465 clear_leaf_cells(page);
466 for (i, (k, v)) in entries.iter().enumerate() {
467 let cell_size = 4 + k.len() + v.len();
468 let cells_start = leaf_cells_start(page);
469 let slot_end = LEAF_SLOT_ARRAY_OFFSET + (i + 1) * 2;
470 if slot_end + cell_size > cells_start {
471 return Err(BTreeError::Corrupted("leaf rebuild overflowed page".into()));
472 }
473 let cell_offset = cells_start - cell_size;
474 {
475 let data = page.as_bytes_mut();
476 data[cell_offset..cell_offset + 2].copy_from_slice(&(k.len() as u16).to_le_bytes());
477 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&(v.len() as u16).to_le_bytes());
478 data[cell_offset + 4..cell_offset + 4 + k.len()].copy_from_slice(k);
479 data[cell_offset + 4 + k.len()..cell_offset + 4 + k.len() + v.len()].copy_from_slice(v);
480 }
481 page.set_free_end(cell_offset as u16);
482 leaf_write_slot(page, i, cell_offset as u16)?;
483 }
484 page.set_cell_count(entries.len() as u16);
485 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + entries.len() * 2) as u16);
486 Ok(())
487}
488
489fn can_insert_leaf(page: &Page, key: &[u8], value: &[u8]) -> bool {
490 let needed = 2 + 4 + key.len() + value.len();
491 leaf_free_bytes(page) >= needed
492}
493
494pub(crate) fn overwrite_leaf_value(
510 page: &mut Page,
511 index: usize,
512 new_value: &[u8],
513) -> BTreeResult<()> {
514 let cell_offset = leaf_read_slot(page, index)?;
515 let data = page.as_bytes();
516 if cell_offset + 4 > data.len() {
517 return Err(BTreeError::Corrupted(
518 "leaf cell header out of bounds".into(),
519 ));
520 }
521 let k_len = u16::from_le_bytes([data[cell_offset], data[cell_offset + 1]]) as usize;
522 let old_v_len = u16::from_le_bytes([data[cell_offset + 2], data[cell_offset + 3]]) as usize;
523 if new_value.len() > old_v_len {
524 return Err(BTreeError::Corrupted(
525 "overwrite_leaf_value: new value larger than slot".into(),
526 ));
527 }
528 let value_start = cell_offset + 4 + k_len;
529 if value_start + new_value.len() > data.len() {
530 return Err(BTreeError::Corrupted(
531 "leaf cell value out of bounds".into(),
532 ));
533 }
534 let data = page.as_bytes_mut();
535 if new_value.len() != old_v_len {
538 let new_len = new_value.len() as u16;
539 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&new_len.to_le_bytes());
540 }
541 data[value_start..value_start + new_value.len()].copy_from_slice(new_value);
542 Ok(())
543}
544
545fn insert_into_leaf(page: &mut Page, key: &[u8], value: &[u8]) -> BTreeResult<()> {
547 let cell_count = page.cell_count() as usize;
549 let mut low = 0;
550 let mut high = cell_count;
551 while low < high {
552 let mid = (low + high) / 2;
553 let (cell_key, _) = read_leaf_cell(page, mid)?;
554 match cell_key.as_slice().cmp(key) {
555 Ordering::Less => low = mid + 1,
556 Ordering::Greater => high = mid,
557 Ordering::Equal => {
558 low = mid + 1;
561 break;
562 }
563 }
564 }
565 let insert_pos = low;
566
567 let cell_size = 4 + key.len() + value.len();
569 let slot_end_after = LEAF_SLOT_ARRAY_OFFSET + (cell_count + 1) * 2;
570 let cells_start = leaf_cells_start(page);
571 if slot_end_after + cell_size > cells_start {
572 return Err(BTreeError::Corrupted("leaf page full".into()));
573 }
574 let cell_offset = cells_start - cell_size;
575 {
576 let data = page.as_bytes_mut();
577 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
578 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&(value.len() as u16).to_le_bytes());
579 data[cell_offset + 4..cell_offset + 4 + key.len()].copy_from_slice(key);
580 data[cell_offset + 4 + key.len()..cell_offset + 4 + key.len() + value.len()]
581 .copy_from_slice(value);
582 }
583 page.set_free_end(cell_offset as u16);
584
585 {
589 let shift_from = leaf_slot_offset_for(insert_pos);
590 let shift_to = shift_from + 2;
591 let shift_len = (cell_count - insert_pos) * 2;
592 if shift_len > 0 {
593 let data = page.as_bytes_mut();
594 data.copy_within(shift_from..shift_from + shift_len, shift_to);
595 }
596 }
597 leaf_write_slot(page, insert_pos, cell_offset as u16)?;
598
599 page.set_cell_count((cell_count + 1) as u16);
601 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + (cell_count + 1) * 2) as u16);
602 Ok(())
603}
604
605fn delete_from_leaf(page: &mut Page, index: usize) -> BTreeResult<()> {
610 let cell_count = page.cell_count() as usize;
611 if index >= cell_count {
612 return Err(BTreeError::Corrupted("delete index out of range".into()));
613 }
614 if index + 1 < cell_count {
615 let shift_from = leaf_slot_offset_for(index + 1);
616 let shift_to = leaf_slot_offset_for(index);
617 let shift_len = (cell_count - index - 1) * 2;
618 let data = page.as_bytes_mut();
619 data.copy_within(shift_from..shift_from + shift_len, shift_to);
620 }
621 page.set_cell_count((cell_count - 1) as u16);
622 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + (cell_count - 1) * 2) as u16);
623 Ok(())
624}
625
626fn clear_leaf_cells(page: &mut Page) {
630 {
631 let data = page.as_bytes_mut();
632 for byte in &mut data[LEAF_SLOT_ARRAY_OFFSET..] {
633 *byte = 0;
634 }
635 }
636 page.set_cell_count(0);
637 page.set_free_start(LEAF_SLOT_ARRAY_OFFSET as u16);
638 page.set_free_end(PAGE_SIZE as u16);
639}
640
641fn init_leaf_links(page: &mut Page, prev: u32, next: u32) {
642 let data = page.as_bytes_mut();
643 data[LEAF_PREV_OFFSET..LEAF_PREV_OFFSET + 4].copy_from_slice(&prev.to_le_bytes());
644 data[LEAF_NEXT_OFFSET..LEAF_NEXT_OFFSET + 4].copy_from_slice(&next.to_le_bytes());
645}
646
647fn read_next_leaf(page: &Page) -> u32 {
648 let data = page.as_bytes();
649 u32::from_le_bytes([
650 data[LEAF_NEXT_OFFSET],
651 data[LEAF_NEXT_OFFSET + 1],
652 data[LEAF_NEXT_OFFSET + 2],
653 data[LEAF_NEXT_OFFSET + 3],
654 ])
655}
656
657#[inline]
660fn leaf_right_sibling(page: &Page) -> u32 {
661 read_next_leaf(page)
662}
663
664fn leaf_high_key(page: &Page) -> BTreeResult<Option<Vec<u8>>> {
668 let cell_count = page.cell_count() as usize;
669 if cell_count == 0 {
670 return Ok(None);
671 }
672 let (key, _) = read_leaf_cell(page, cell_count - 1)?;
673 Ok(Some(key))
674}
675
676fn set_prev_leaf(page: &mut Page, prev: u32) {
677 let data = page.as_bytes_mut();
678 data[LEAF_PREV_OFFSET..LEAF_PREV_OFFSET + 4].copy_from_slice(&prev.to_le_bytes());
679}
680
681fn set_next_leaf(page: &mut Page, next: u32) {
682 let data = page.as_bytes_mut();
683 data[LEAF_NEXT_OFFSET..LEAF_NEXT_OFFSET + 4].copy_from_slice(&next.to_le_bytes());
684}
685
686#[inline]
701fn interior_slot_offset_for(index: usize) -> usize {
702 INTERIOR_DATA_OFFSET + index * 2
703}
704
705#[inline]
706fn interior_read_slot(page: &Page, index: usize) -> BTreeResult<usize> {
707 let data = page.as_bytes();
708 let slot_pos = interior_slot_offset_for(index);
709 if slot_pos + 2 > PAGE_SIZE {
710 return Err(BTreeError::Corrupted(
711 "interior slot array overflows page".into(),
712 ));
713 }
714 Ok(u16::from_le_bytes([data[slot_pos], data[slot_pos + 1]]) as usize)
715}
716
717#[inline]
718fn interior_write_slot(page: &mut Page, index: usize, cell_offset: u16) -> BTreeResult<()> {
719 let data = page.as_bytes_mut();
720 let slot_pos = interior_slot_offset_for(index);
721 if slot_pos + 2 > PAGE_SIZE {
722 return Err(BTreeError::Corrupted(
723 "interior slot array overflows page".into(),
724 ));
725 }
726 data[slot_pos..slot_pos + 2].copy_from_slice(&cell_offset.to_le_bytes());
727 Ok(())
728}
729
730#[inline]
731fn interior_cells_start(page: &Page) -> usize {
732 let end = page.free_end() as usize;
733 if end == 0 {
734 PAGE_SIZE
735 } else {
736 end
737 }
738}
739
740#[inline]
741fn interior_free_bytes(page: &Page) -> usize {
742 let slot_end = INTERIOR_DATA_OFFSET + (page.cell_count() as usize) * 2;
743 interior_cells_start(page).saturating_sub(slot_end)
744}
745
746fn read_interior_cell(page: &Page, index: usize) -> BTreeResult<(Vec<u8>, u32)> {
747 let cell_count = page.cell_count() as usize;
748 if index >= cell_count {
749 return Err(BTreeError::Corrupted("Cell index out of range".into()));
750 }
751 let offset = interior_read_slot(page, index)?;
752 let data = page.as_bytes();
753 if offset + 2 > PAGE_SIZE {
754 return Err(BTreeError::Corrupted(
755 "interior cell header out of range".into(),
756 ));
757 }
758 let key_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
759 let end = offset + 2 + key_len + 4;
760 if end > PAGE_SIZE {
761 return Err(BTreeError::Corrupted(
762 "interior cell body out of range".into(),
763 ));
764 }
765 let key = data[offset + 2..offset + 2 + key_len].to_vec();
766 let child = u32::from_le_bytes([
767 data[offset + 2 + key_len],
768 data[offset + 2 + key_len + 1],
769 data[offset + 2 + key_len + 2],
770 data[offset + 2 + key_len + 3],
771 ]);
772 Ok((key, child))
773}
774
775fn read_interior_keys_children(page: &Page) -> BTreeResult<(Vec<Vec<u8>>, Vec<u32>)> {
776 let cell_count = page.cell_count() as usize;
777 let mut keys = Vec::with_capacity(cell_count);
778 let mut children = Vec::with_capacity(cell_count + 1);
779
780 for i in 0..cell_count {
781 let (key, child) = read_interior_cell(page, i)?;
782 keys.push(key);
783 children.push(child);
784 }
785
786 if cell_count == 0 {
787 let right_child = page.right_child();
788 if right_child != 0 {
789 children.push(right_child);
790 }
791 } else {
792 children.push(page.right_child());
793 }
794
795 Ok((keys, children))
796}
797
798fn write_interior_entries(page: &mut Page, keys: &[Vec<u8>], children: &[u32]) -> BTreeResult<()> {
803 if !keys.is_empty() && children.len() != keys.len() + 1 {
804 return Err(BTreeError::Corrupted(
805 "Interior keys/children length mismatch".into(),
806 ));
807 }
808
809 clear_interior_cells(page);
810 if keys.is_empty() {
811 let right_child = children.first().copied().unwrap_or(0);
812 page.set_right_child(right_child);
813 return Ok(());
814 }
815
816 for (i, key) in keys.iter().enumerate() {
817 let cell_size = 2 + key.len() + 4;
818 let cells_start = interior_cells_start(page);
819 let slot_end = INTERIOR_DATA_OFFSET + (i + 1) * 2;
820 if slot_end + cell_size > cells_start {
821 return Err(BTreeError::Corrupted(
822 "interior rebuild overflowed page".into(),
823 ));
824 }
825 let cell_offset = cells_start - cell_size;
826 {
827 let data = page.as_bytes_mut();
828 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
829 data[cell_offset + 2..cell_offset + 2 + key.len()].copy_from_slice(key);
830 data[cell_offset + 2 + key.len()..cell_offset + 2 + key.len() + 4]
831 .copy_from_slice(&children[i].to_le_bytes());
832 }
833 page.set_free_end(cell_offset as u16);
834 interior_write_slot(page, i, cell_offset as u16)?;
835 }
836 page.set_cell_count(keys.len() as u16);
837 page.set_free_start((INTERIOR_DATA_OFFSET + keys.len() * 2) as u16);
838 page.set_right_child(*children.last().ok_or_else(|| {
839 BTreeError::Corrupted("write_interior_entries: children empty with non-empty keys".into())
840 })?);
841 Ok(())
842}
843
844fn can_insert_interior(page: &Page, key: &[u8]) -> bool {
845 let needed = 2 + 2 + key.len() + 4;
846 interior_free_bytes(page) >= needed
847}
848
849fn insert_into_interior(
854 page: &mut Page,
855 key: &[u8],
856 left_child: u32,
857 right_child: u32,
858) -> BTreeResult<()> {
859 let cell_count = page.cell_count() as usize;
861 let mut low = 0;
862 let mut high = cell_count;
863 while low < high {
864 let mid = (low + high) / 2;
865 let (cell_key, _) = read_interior_cell(page, mid)?;
866 match cell_key.as_slice().cmp(key) {
867 Ordering::Less => low = mid + 1,
868 Ordering::Greater | Ordering::Equal => high = mid,
869 }
870 }
871 let insert_pos = low;
872
873 if insert_pos < cell_count {
878 let old_offset = interior_read_slot(page, insert_pos)?;
879 let data = page.as_bytes();
880 let key_len = u16::from_le_bytes([data[old_offset], data[old_offset + 1]]) as usize;
881 let child_pos = old_offset + 2 + key_len;
882 let data = page.as_bytes_mut();
883 data[child_pos..child_pos + 4].copy_from_slice(&right_child.to_le_bytes());
884 } else {
885 page.set_right_child(right_child);
886 }
887
888 let cell_size = 2 + key.len() + 4;
890 let slot_end_after = INTERIOR_DATA_OFFSET + (cell_count + 1) * 2;
891 let cells_start = interior_cells_start(page);
892 if slot_end_after + cell_size > cells_start {
893 return Err(BTreeError::Corrupted("interior page full".into()));
894 }
895 let cell_offset = cells_start - cell_size;
896 {
897 let data = page.as_bytes_mut();
898 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
899 data[cell_offset + 2..cell_offset + 2 + key.len()].copy_from_slice(key);
900 data[cell_offset + 2 + key.len()..cell_offset + 2 + key.len() + 4]
901 .copy_from_slice(&left_child.to_le_bytes());
902 }
903 page.set_free_end(cell_offset as u16);
904
905 {
908 let shift_from = interior_slot_offset_for(insert_pos);
909 let shift_to = shift_from + 2;
910 let shift_len = (cell_count - insert_pos) * 2;
911 if shift_len > 0 {
912 let data = page.as_bytes_mut();
913 data.copy_within(shift_from..shift_from + shift_len, shift_to);
914 }
915 }
916 interior_write_slot(page, insert_pos, cell_offset as u16)?;
917 page.set_cell_count((cell_count + 1) as u16);
918 page.set_free_start((INTERIOR_DATA_OFFSET + (cell_count + 1) * 2) as u16);
919 Ok(())
920}
921
922fn clear_interior_cells(page: &mut Page) {
923 {
924 let data = page.as_bytes_mut();
925 for byte in &mut data[INTERIOR_DATA_OFFSET..] {
926 *byte = 0;
927 }
928 }
929 page.set_cell_count(0);
930 page.set_free_start(INTERIOR_DATA_OFFSET as u16);
931 page.set_free_end(PAGE_SIZE as u16);
932}
933
934#[cfg(test)]
935mod tests {
936 use super::*;
937 use std::path::PathBuf;
938
939 struct TempDbPath(PathBuf);
946 impl std::ops::Deref for TempDbPath {
947 type Target = PathBuf;
948 fn deref(&self) -> &PathBuf {
949 &self.0
950 }
951 }
952 impl AsRef<std::path::Path> for TempDbPath {
953 fn as_ref(&self) -> &std::path::Path {
954 self.0.as_ref()
955 }
956 }
957 impl Drop for TempDbPath {
958 fn drop(&mut self) {
959 cleanup(&self.0);
960 }
961 }
962
963 fn temp_db_path() -> TempDbPath {
964 use std::sync::atomic::{AtomicU64, Ordering};
965 static COUNTER: AtomicU64 = AtomicU64::new(0);
966 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
967 let mut path = std::env::temp_dir();
968 path.push(format!("reddb_btree_test_{}_{}.db", std::process::id(), id));
969 TempDbPath(path)
970 }
971
972 fn cleanup(path: &PathBuf) {
973 if let (Some(dir), Some(name)) = (
977 path.parent(),
978 path.file_name().and_then(|name| name.to_str()),
979 ) {
980 if let Ok(entries) = std::fs::read_dir(dir) {
981 for entry in entries.flatten() {
982 if entry
983 .file_name()
984 .to_str()
985 .is_some_and(|entry_name| entry_name.starts_with(name))
986 {
987 let _ = std::fs::remove_file(entry.path());
988 }
989 }
990 }
991 }
992 }
993
994 #[test]
995 fn test_btree_empty() {
996 let path = temp_db_path();
997 cleanup(&path);
998
999 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1000 let tree = BTree::new(pager);
1001
1002 assert!(tree.is_empty());
1003 assert_eq!(tree.root_page_id(), 0);
1004 assert_eq!(tree.get(b"key").expect("get() should succeed"), None);
1005 assert_eq!(tree.count().expect("count() should succeed"), 0);
1006
1007 cleanup(&path);
1008 }
1009
1010 #[test]
1011 fn test_btree_single_insert() {
1012 let path = temp_db_path();
1013 cleanup(&path);
1014
1015 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1016 let tree = BTree::new(pager);
1017
1018 tree.insert(b"hello", b"world")
1019 .expect("insert() should succeed");
1020
1021 assert!(!tree.is_empty());
1022 assert_eq!(
1023 tree.get(b"hello").expect("get() should succeed"),
1024 Some(b"world".to_vec())
1025 );
1026 assert_eq!(tree.get(b"other").expect("get() should succeed"), None);
1027 assert_eq!(tree.count().expect("count() should succeed"), 1);
1028
1029 cleanup(&path);
1030 }
1031
1032 #[test]
1033 fn test_btree_multiple_inserts() {
1034 let path = temp_db_path();
1035 cleanup(&path);
1036
1037 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1038 let tree = BTree::new(pager);
1039
1040 tree.insert(b"c", b"3").expect("insert() should succeed");
1041 tree.insert(b"a", b"1").expect("insert() should succeed");
1042 tree.insert(b"b", b"2").expect("insert() should succeed");
1043
1044 assert_eq!(
1045 tree.get(b"a").expect("get() should succeed"),
1046 Some(b"1".to_vec())
1047 );
1048 assert_eq!(
1049 tree.get(b"b").expect("get() should succeed"),
1050 Some(b"2".to_vec())
1051 );
1052 assert_eq!(
1053 tree.get(b"c").expect("get() should succeed"),
1054 Some(b"3".to_vec())
1055 );
1056 assert_eq!(tree.count().expect("count() should succeed"), 3);
1057
1058 cleanup(&path);
1059 }
1060
1061 #[test]
1062 fn test_btree_duplicate_key() {
1063 let path = temp_db_path();
1064 cleanup(&path);
1065
1066 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1067 let tree = BTree::new(pager);
1068
1069 tree.insert(b"key", b"value1")
1070 .expect("insert() should succeed");
1071 let result = tree.insert(b"key", b"value2");
1072
1073 assert!(matches!(result, Err(BTreeError::DuplicateKey)));
1074 assert_eq!(
1075 tree.get(b"key").expect("get() should succeed"),
1076 Some(b"value1".to_vec())
1077 );
1078
1079 cleanup(&path);
1080 }
1081
1082 #[test]
1083 fn test_btree_delete() {
1084 let path = temp_db_path();
1085 cleanup(&path);
1086
1087 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1088 let tree = BTree::new(pager);
1089
1090 tree.insert(b"a", b"1").expect("insert() should succeed");
1091 tree.insert(b"b", b"2").expect("insert() should succeed");
1092 tree.insert(b"c", b"3").expect("insert() should succeed");
1093
1094 assert!(tree.delete(b"b").expect("delete() should succeed"));
1095 assert!(!tree.delete(b"d").expect("delete() should succeed"));
1096
1097 assert_eq!(
1098 tree.get(b"a").expect("get() should succeed"),
1099 Some(b"1".to_vec())
1100 );
1101 assert_eq!(tree.get(b"b").expect("get() should succeed"), None);
1102 assert_eq!(
1103 tree.get(b"c").expect("get() should succeed"),
1104 Some(b"3".to_vec())
1105 );
1106 assert_eq!(tree.count().expect("count() should succeed"), 2);
1107
1108 cleanup(&path);
1109 }
1110
1111 #[test]
1112 fn test_btree_delete_rebalance_removes_empty_leaf() {
1113 let path = temp_db_path();
1114 cleanup(&path);
1115
1116 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1117 let tree = BTree::new(pager.clone());
1118
1119 let value = vec![b'v'; 200];
1120 for i in 0..300u32 {
1121 let key = format!("key{:03}", i);
1122 tree.insert(key.as_bytes(), &value)
1123 .expect("insert() should succeed");
1124 }
1125
1126 let root_id = tree.root_page_id();
1127 let first_leaf = tree
1128 .find_first_leaf(root_id)
1129 .expect("find_first_leaf() should succeed");
1130 let mut leaf_ids = Vec::new();
1131 let mut current = first_leaf;
1132 loop {
1133 leaf_ids.push(current);
1134 let page = pager
1135 .read_page(current)
1136 .expect("read_page() should succeed");
1137 let next = read_next_leaf(&page);
1138 if next == 0 {
1139 break;
1140 }
1141 current = next;
1142 }
1143
1144 assert!(leaf_ids.len() >= 3);
1145
1146 let target_leaf = leaf_ids[1];
1147 let page = pager
1148 .read_page(target_leaf)
1149 .expect("read_page() should succeed");
1150 let cell_count = page.cell_count() as usize;
1151 let mut keys = Vec::with_capacity(cell_count);
1152 for i in 0..cell_count {
1153 let (key, _) = read_leaf_cell(&page, i).expect("read_leaf_cell() should succeed");
1154 keys.push(key);
1155 }
1156
1157 for key in &keys {
1158 tree.delete(key).expect("delete() should succeed");
1159 }
1160
1161 let expected = 300 - keys.len();
1162 assert_eq!(tree.count().expect("count() should succeed"), expected);
1163
1164 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1165 let mut results = Vec::new();
1166 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1167 results.push(key);
1168 }
1169
1170 assert_eq!(results.len(), expected);
1171 let last_key = format!("key{:03}", 299).into_bytes();
1172 assert_eq!(results.last(), Some(&last_key));
1173
1174 cleanup(&path);
1175 }
1176
1177 #[test]
1178 fn test_btree_cursor() {
1179 let path = temp_db_path();
1180 cleanup(&path);
1181
1182 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1183 let tree = BTree::new(pager);
1184
1185 tree.insert(b"c", b"3").expect("insert() should succeed");
1186 tree.insert(b"a", b"1").expect("insert() should succeed");
1187 tree.insert(b"b", b"2").expect("insert() should succeed");
1188
1189 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1190 let mut results: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
1191
1192 while let Some(entry) = cursor.next().expect("next() should succeed") {
1193 results.push(entry);
1194 }
1195
1196 assert_eq!(results.len(), 3);
1197 assert_eq!(results[0], (b"a".to_vec(), b"1".to_vec()));
1198 assert_eq!(results[1], (b"b".to_vec(), b"2".to_vec()));
1199 assert_eq!(results[2], (b"c".to_vec(), b"3".to_vec()));
1200
1201 cleanup(&path);
1202 }
1203
1204 #[test]
1205 fn test_btree_range() {
1206 let path = temp_db_path();
1207 cleanup(&path);
1208
1209 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1210 let tree = BTree::new(pager);
1211
1212 for i in 0..10u8 {
1213 let key = format!("key{:02}", i);
1214 let value = format!("val{:02}", i);
1215 tree.insert(key.as_bytes(), value.as_bytes())
1216 .expect("insert() should succeed");
1217 }
1218
1219 let results = tree
1220 .range(b"key03", b"key06")
1221 .expect("range() should succeed");
1222
1223 assert_eq!(results.len(), 4);
1224 assert_eq!(results[0].0, b"key03".to_vec());
1225 assert_eq!(results[3].0, b"key06".to_vec());
1226
1227 cleanup(&path);
1228 }
1229
1230 #[test]
1231 fn test_btree_large_keys() {
1232 let path = temp_db_path();
1233 cleanup(&path);
1234
1235 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1236 let tree = BTree::new(pager);
1237
1238 let key = vec![b'x'; 500];
1239 let value = vec![b'y'; 500];
1240
1241 tree.insert(&key, &value).expect("insert() should succeed");
1242 assert_eq!(tree.get(&key).expect("get() should succeed"), Some(value));
1243
1244 cleanup(&path);
1245 }
1246
1247 #[test]
1248 fn test_btree_key_too_large() {
1249 let path = temp_db_path();
1250 cleanup(&path);
1251
1252 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1253 let tree = BTree::new(pager);
1254
1255 let key = vec![b'x'; MAX_KEY_SIZE + 1];
1256 let result = tree.insert(&key, b"value");
1257
1258 assert!(matches!(result, Err(BTreeError::KeyTooLarge(_))));
1259
1260 cleanup(&path);
1261 }
1262
1263 #[test]
1264 fn test_btree_many_inserts() {
1265 let path = temp_db_path();
1266 cleanup(&path);
1267
1268 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1269 let tree = BTree::new(pager);
1270
1271 for i in 0..100u32 {
1273 let key = format!("key{:08}", i);
1274 let value = format!("value{:08}", i);
1275 tree.insert(key.as_bytes(), value.as_bytes())
1276 .expect("insert() should succeed");
1277 }
1278
1279 for i in 0..100u32 {
1281 let key = format!("key{:08}", i);
1282 let expected = format!("value{:08}", i);
1283 assert_eq!(
1284 tree.get(key.as_bytes()).expect("get() should succeed"),
1285 Some(expected.into_bytes())
1286 );
1287 }
1288
1289 assert_eq!(tree.count().expect("count() should succeed"), 100);
1290
1291 cleanup(&path);
1292 }
1293
1294 #[test]
1295 fn test_btree_bulk_insert_sorted_preserves_leaf_layout() {
1296 let path = temp_db_path();
1297 cleanup(&path);
1298
1299 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1300 let tree = BTree::new(pager);
1301
1302 let items: Vec<(Vec<u8>, Vec<u8>)> = (0..240u32)
1303 .map(|i| {
1304 (
1305 format!("key{:08}", i).into_bytes(),
1306 vec![b'v'; 32 + (i as usize % 7)],
1307 )
1308 })
1309 .collect();
1310
1311 tree.bulk_insert_sorted(&items)
1312 .expect("bulk_insert_sorted() should succeed");
1313
1314 assert_eq!(tree.count().expect("count() should succeed"), items.len());
1315
1316 for (key, value) in &items {
1317 assert_eq!(
1318 tree.get(key).expect("get() should succeed"),
1319 Some(value.clone())
1320 );
1321 }
1322
1323 cleanup(&path);
1324 }
1325
1326 mod prop_upsert_batch_sorted {
1336 use super::*;
1337 use proptest::prelude::*;
1338 use std::collections::BTreeMap;
1339
1340 fn populate_tree(seed: &[(Vec<u8>, Vec<u8>)]) -> (BTree, TempDbPath, Arc<Pager>) {
1343 let path = temp_db_path();
1344 cleanup(&path);
1345 let pager =
1346 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1347 let tree = BTree::new(Arc::clone(&pager));
1348 for (k, v) in seed {
1349 tree.upsert(k, v).expect("upsert() should succeed");
1350 }
1351 (tree, path, pager)
1352 }
1353
1354 fn key_strategy() -> impl Strategy<Value = Vec<u8>> {
1355 prop::collection::vec(0u8..16u8, 1..=4)
1359 }
1360
1361 fn value_strategy() -> impl Strategy<Value = Vec<u8>> {
1362 prop::collection::vec(0u8..255u8, 0..=8)
1366 }
1367
1368 fn pair_strategy() -> impl Strategy<Value = (Vec<u8>, Vec<u8>)> {
1369 (key_strategy(), value_strategy())
1370 }
1371
1372 proptest! {
1373 #![proptest_config(ProptestConfig {
1374 cases: 64,
1375 .. ProptestConfig::default()
1376 })]
1377
1378 #[test]
1379 fn batch_upsert_matches_loop_upsert(
1380 seed in prop::collection::vec(pair_strategy(), 0..50),
1381 batch in prop::collection::vec(pair_strategy(), 1..200),
1382 ) {
1383 let mut seed_map: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
1386 for (k, v) in seed {
1387 seed_map.insert(k, v);
1388 }
1389 let seed: Vec<(Vec<u8>, Vec<u8>)> = seed_map.into_iter().collect();
1390
1391 let (baseline, baseline_path, _baseline_pager) = populate_tree(&seed);
1393 for (k, v) in &batch {
1394 baseline.upsert(k, v).expect("upsert() should succeed");
1395 }
1396
1397 let (candidate, candidate_path, _candidate_pager) = populate_tree(&seed);
1400 let mut sorted = batch.clone();
1401 sorted.sort_by(|a, b| a.0.cmp(&b.0));
1402 candidate.upsert_batch_sorted(&sorted).expect("upsert_batch_sorted() should succeed");
1403
1404 let mut expected: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
1407 for (k, v) in &seed {
1408 expected.insert(k.clone(), v.clone());
1409 }
1410 for (k, v) in &batch {
1411 expected.insert(k.clone(), v.clone());
1412 }
1413
1414 for (k, v) in &expected {
1416 let baseline_got = baseline.get(k).expect("get() should succeed");
1417 let candidate_got = candidate.get(k).expect("get() should succeed");
1418 prop_assert_eq!(baseline_got.as_ref(), Some(v));
1419 prop_assert_eq!(candidate_got.as_ref(), Some(v));
1420 }
1421
1422 let collect = |t: &BTree| -> Vec<(Vec<u8>, Vec<u8>)> {
1424 let mut out = Vec::new();
1425 let mut cur = t.cursor_first().expect("cursor_first() should succeed");
1426 while let Some(entry) = cur.next().expect("next() should succeed") {
1427 out.push(entry);
1428 }
1429 out
1430 };
1431 let baseline_contents = collect(&baseline);
1432 let candidate_contents = collect(&candidate);
1433 prop_assert_eq!(&baseline_contents, &candidate_contents);
1434
1435 let expected_contents: Vec<(Vec<u8>, Vec<u8>)> =
1437 expected.into_iter().collect();
1438 prop_assert_eq!(&candidate_contents, &expected_contents);
1439
1440 drop(baseline);
1448 drop(candidate);
1449 drop(_baseline_pager);
1450 drop(_candidate_pager);
1451 cleanup(&baseline_path);
1452 cleanup(&candidate_path);
1453 }
1454 }
1455 }
1456
1457 #[test]
1458 fn test_btree_sorted_iteration() {
1459 let path = temp_db_path();
1460 cleanup(&path);
1461
1462 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1463 let tree = BTree::new(pager);
1464
1465 let keys = vec![50, 25, 75, 10, 30, 60, 80, 5, 15, 27, 35, 55, 65, 77, 90];
1467 for k in &keys {
1468 let key = format!("{:03}", k);
1469 tree.insert(key.as_bytes(), key.as_bytes())
1470 .expect("insert() should succeed");
1471 }
1472
1473 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1475 let mut prev: Option<Vec<u8>> = None;
1476
1477 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1478 if let Some(p) = &prev {
1479 assert!(p < &key, "Keys not in sorted order");
1480 }
1481 prev = Some(key);
1482 }
1483
1484 cleanup(&path);
1485 }
1486
1487 #[test]
1492 fn concurrent_writers_on_one_tree_keep_every_key() {
1493 const WRITER_COUNT: u32 = 4;
1494 const KEY_COUNT_PER_WRITER: u32 = 2_000;
1495
1496 let path = temp_db_path();
1497 cleanup(&path);
1498 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1499 let tree = Arc::new(BTree::new(pager));
1500 let value = vec![7u8; 96];
1501
1502 let writers: Vec<_> = (0..WRITER_COUNT)
1503 .map(|writer| {
1504 let tree = Arc::clone(&tree);
1505 let value = value.clone();
1506 std::thread::spawn(move || {
1507 for i in 0..KEY_COUNT_PER_WRITER {
1508 let key = (i * WRITER_COUNT + writer).to_be_bytes();
1510 tree.insert(&key, &value).expect("insert() should succeed");
1511 if i % 4 == 0 {
1513 assert!(tree.delete(&key).expect("delete() should succeed"));
1514 tree.upsert(&key, &value).expect("upsert() should succeed");
1515 }
1516 }
1517 })
1518 })
1519 .collect();
1520 for writer in writers {
1521 writer.join().expect("writer thread panicked");
1522 }
1523
1524 let key_count = WRITER_COUNT * KEY_COUNT_PER_WRITER;
1525 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1526 let mut seen = Vec::new();
1527 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1528 seen.push(u32::from_be_bytes(
1529 key.as_slice().try_into().expect("invariant: 4-byte keys"),
1530 ));
1531 }
1532 let expected: Vec<u32> = (0..key_count).collect();
1533 assert_eq!(seen, expected, "every key exactly once, in order");
1534 for key in 0..key_count {
1535 assert!(
1536 tree.get(&key.to_be_bytes())
1537 .expect("get() should succeed")
1538 .is_some(),
1539 "key {key} must be reachable from the root"
1540 );
1541 }
1542
1543 cleanup(&path);
1544 }
1545}
1546
1547#[cfg(test)]
1552mod overflow_pipeline_tests {
1553 use super::value_layout::{MAX_VALUE_SIZE, OVERFLOW_THRESHOLD};
1554 use super::*;
1555 use std::path::PathBuf;
1556
1557 fn temp_db_path(tag: &str) -> PathBuf {
1558 use std::sync::atomic::{AtomicU64, Ordering};
1559 static COUNTER: AtomicU64 = AtomicU64::new(0);
1560 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
1561 let mut path = std::env::temp_dir();
1562 path.push(format!(
1563 "reddb_btree_spill_{}_{}_{}.db",
1564 tag,
1565 std::process::id(),
1566 id
1567 ));
1568 path
1569 }
1570
1571 fn cleanup(path: &std::path::Path) {
1572 let _ = std::fs::remove_file(path);
1573 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
1574 let _ = std::fs::remove_file(sidecar);
1575 }
1576 }
1577
1578 #[test]
1583 fn inline_path_allocates_no_overflow_pages() {
1584 let path = temp_db_path("inline_no_overflow");
1585 cleanup(&path);
1586 {
1587 let pager =
1588 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1589 let tree = BTree::new(pager.clone());
1590 let before = pager.page_count().expect("page_count() should succeed");
1591
1592 let value = vec![0x5Au8; OVERFLOW_THRESHOLD - 1];
1593 tree.insert(b"k", &value).expect("insert() should succeed");
1594
1595 let after = pager.page_count().expect("page_count() should succeed");
1596 assert_eq!(
1599 after - before,
1600 1,
1601 "inline value must allocate only the root leaf"
1602 );
1603 assert_eq!(tree.get(b"k").expect("get() should succeed"), Some(value));
1604 }
1605 cleanup(&path);
1606 }
1607
1608 #[test]
1612 fn compressible_44kb_round_trips() {
1613 let path = temp_db_path("compressible_44kb");
1614 cleanup(&path);
1615 {
1616 let pager =
1617 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1618 let tree = BTree::new(pager);
1619
1620 let snippet = "# heading\n- bullet line that says the same thing over and over\n";
1622 let mut value = Vec::with_capacity(44 * 1024);
1623 while value.len() < 44 * 1024 {
1624 value.extend_from_slice(snippet.as_bytes());
1625 }
1626 assert!(value.len() > OVERFLOW_THRESHOLD);
1627
1628 tree.insert(b"doc", &value)
1629 .expect("insert() should succeed");
1630 assert_eq!(tree.get(b"doc").expect("get() should succeed"), Some(value));
1631 }
1632 cleanup(&path);
1633 }
1634
1635 #[test]
1638 fn incompressible_5mb_spills_and_round_trips() {
1639 let path = temp_db_path("incompressible_5mb");
1640 cleanup(&path);
1641 {
1642 let pager =
1643 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1644 let tree = BTree::new(pager.clone());
1645
1646 let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
1649 let value: Vec<u8> = (0..5 * 1024 * 1024)
1650 .map(|_| {
1651 state ^= state << 13;
1652 state ^= state >> 7;
1653 state ^= state << 17;
1654 state as u8
1655 })
1656 .collect();
1657
1658 let before = pager.page_count().expect("page_count() should succeed");
1659 tree.insert(b"blob", &value)
1660 .expect("insert() should succeed");
1661 let after = pager.page_count().expect("page_count() should succeed");
1662 assert!(
1665 after - before > 2,
1666 "5 MB spill must allocate overflow pages (alloc'd {} pages)",
1667 after - before
1668 );
1669
1670 assert_eq!(
1671 tree.get(b"blob").expect("get() should succeed").as_deref(),
1672 Some(value.as_slice())
1673 );
1674 }
1675 cleanup(&path);
1676 }
1677
1678 #[test]
1682 fn value_above_ceiling_rejected_without_allocation() {
1683 let path = temp_db_path("ceiling_reject");
1684 cleanup(&path);
1685 {
1686 let pager =
1687 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1688 let tree = BTree::new(pager.clone());
1689 let before = pager.page_count().expect("page_count() should succeed");
1690
1691 let value = vec![0u8; MAX_VALUE_SIZE + 1];
1692 let err = tree.insert(b"too_big", &value).unwrap_err();
1693 assert!(matches!(err, BTreeError::ValueTooLarge(_)));
1694
1695 assert_eq!(
1696 pager.page_count().expect("page_count() should succeed"),
1697 before,
1698 "rejected value must not allocate pages"
1699 );
1700 assert!(tree.is_empty());
1702 }
1703 cleanup(&path);
1704 }
1705
1706 #[test]
1711 fn bulk_insert_skips_oversized_row_keeps_rest() {
1712 let path = temp_db_path("bulk_mixed");
1713 cleanup(&path);
1714 {
1715 let pager =
1716 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1717 let tree = BTree::new(pager);
1718
1719 let mut items: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
1720 for i in 0..5u32 {
1722 items.push((format!("k{:04}", i).into_bytes(), vec![b'a' + i as u8; 64]));
1723 }
1724 items.push((b"k0005_huge".to_vec(), vec![0u8; MAX_VALUE_SIZE + 1]));
1726 for i in 6..11u32 {
1728 items.push((format!("k{:04}", i).into_bytes(), vec![b'z' - i as u8; 32]));
1729 }
1730
1731 let res = tree.bulk_insert_sorted(&items);
1732 assert!(matches!(res, Err(BTreeError::ValueTooLarge(_))));
1733
1734 for (k, v) in items.iter().filter(|(_, v)| v.len() <= MAX_VALUE_SIZE) {
1736 assert_eq!(
1737 tree.get(k).expect("get() should succeed").as_deref(),
1738 Some(v.as_slice()),
1739 "valid row {:?} must land",
1740 k
1741 );
1742 }
1743 assert_eq!(tree.get(b"k0005_huge").expect("get() should succeed"), None);
1745 }
1746 cleanup(&path);
1747 }
1748
1749 #[test]
1753 fn large_values_persist_transparently() {
1754 let path = temp_db_path("large_transparent");
1755 cleanup(&path);
1756 {
1757 let pager =
1758 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1759 let tree = BTree::new(pager);
1760
1761 for (i, size) in [
1766 OVERFLOW_THRESHOLD + 1,
1767 OVERFLOW_THRESHOLD * 2,
1768 64 * 1024,
1769 1 * 1024 * 1024,
1770 ]
1771 .iter()
1772 .enumerate()
1773 {
1774 let value: Vec<u8> = (0..*size).map(|n| ((n + i) & 0xff) as u8).collect();
1775 let key = format!("size{:08}", size).into_bytes();
1776 tree.insert(&key, &value).expect("insert() should succeed");
1777 assert_eq!(
1778 tree.get(&key).expect("get() should succeed").as_deref(),
1779 Some(value.as_slice()),
1780 "size {} must round-trip",
1781 size
1782 );
1783 }
1784 }
1785 cleanup(&path);
1786 }
1787}
1788
1789#[cfg(test)]
1792mod overflow_free_tests {
1793 use super::value_layout::OVERFLOW_THRESHOLD;
1794 use super::*;
1795 use std::path::PathBuf;
1796
1797 fn temp_db_path(tag: &str) -> PathBuf {
1798 use std::sync::atomic::{AtomicU64, Ordering};
1799 static COUNTER: AtomicU64 = AtomicU64::new(0);
1800 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
1801 let mut path = std::env::temp_dir();
1802 path.push(format!(
1803 "reddb_btree_free_{}_{}_{}.db",
1804 tag,
1805 std::process::id(),
1806 id
1807 ));
1808 path
1809 }
1810
1811 fn cleanup(path: &std::path::Path) {
1812 let _ = std::fs::remove_file(path);
1813 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
1814 let _ = std::fs::remove_file(sidecar);
1815 }
1816 }
1817
1818 fn incompressible(seed: u64, len: usize) -> Vec<u8> {
1819 let mut state = seed | 1;
1820 (0..len)
1821 .map(|_| {
1822 state ^= state << 13;
1823 state ^= state >> 7;
1824 state ^= state << 17;
1825 state as u8
1826 })
1827 .collect()
1828 }
1829
1830 #[test]
1836 fn delete_spilled_value_frees_overflow_chain() {
1837 let path = temp_db_path("delete_frees");
1838 cleanup(&path);
1839 {
1840 let pager =
1841 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1842 let tree = BTree::new(pager.clone());
1843
1844 let v1 = incompressible(0xA11CE, 2 * 1024 * 1024);
1846 tree.insert(b"k", &v1).expect("insert() should succeed");
1847 let after_first = pager.page_count().expect("page_count() should succeed");
1848
1849 assert!(tree.delete(b"k").expect("delete() should succeed"));
1851
1852 let v2 = incompressible(0xB0B, 2 * 1024 * 1024);
1855 tree.insert(b"k", &v2).expect("insert() should succeed");
1856 let after_second = pager.page_count().expect("page_count() should succeed");
1857
1858 assert_eq!(
1859 after_second, after_first,
1860 "second spill must reuse freed pages, not grow the file"
1861 );
1862 assert_eq!(
1863 tree.get(b"k").expect("get() should succeed").as_deref(),
1864 Some(v2.as_slice())
1865 );
1866 }
1867 cleanup(&path);
1868 }
1869
1870 #[test]
1875 fn shrinking_update_to_inline_frees_chain() {
1876 let path = temp_db_path("shrink_to_inline");
1877 cleanup(&path);
1878 {
1879 let pager =
1880 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1881 let tree = BTree::new(pager.clone());
1882
1883 let big = incompressible(0xDEAD, 2 * 1024 * 1024);
1884 tree.insert(b"k", &big).expect("insert() should succeed");
1885 let hwm = pager.page_count().expect("page_count() should succeed");
1886
1887 let small = b"tiny".to_vec();
1889 tree.upsert(b"k", &small).expect("upsert() should succeed");
1890 assert_eq!(
1891 tree.get(b"k").expect("get() should succeed").as_deref(),
1892 Some(small.as_slice())
1893 );
1894
1895 let big2 = incompressible(0xBEEF, 2 * 1024 * 1024);
1899 tree.insert(b"k2", &big2).expect("insert() should succeed");
1900 assert_eq!(
1901 pager.page_count().expect("page_count() should succeed"),
1902 hwm,
1903 "shrinking update must free the chain — replacement spill should reuse"
1904 );
1905 assert_eq!(
1906 tree.get(b"k2").expect("get() should succeed").as_deref(),
1907 Some(big2.as_slice())
1908 );
1909 }
1910 cleanup(&path);
1911 }
1912
1913 #[test]
1918 fn shrinking_update_to_shorter_spill_frees_old_chain() {
1919 let path = temp_db_path("shrink_spill_to_spill");
1920 cleanup(&path);
1921 {
1922 let pager =
1923 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1924 let tree = BTree::new(pager.clone());
1925
1926 let big = incompressible(0xF00D, 3 * 1024 * 1024);
1930 tree.insert(b"k", &big).expect("insert() should succeed");
1931 let hwm_after_big = pager.page_count().expect("page_count() should succeed");
1932
1933 let smaller = incompressible(0xCAFE, 1 * 1024 * 1024);
1944 tree.upsert(b"k", &smaller)
1945 .expect("upsert() should succeed");
1946 assert_eq!(
1947 tree.get(b"k").expect("get() should succeed").as_deref(),
1948 Some(smaller.as_slice())
1949 );
1950 let hwm_after_upsert = pager.page_count().expect("page_count() should succeed");
1951
1952 assert!(tree.delete(b"k").expect("delete() should succeed"));
1953
1954 let again = incompressible(0x1234, 3 * 1024 * 1024);
1955 tree.insert(b"k", &again).expect("insert() should succeed");
1956 assert!(
1957 pager.page_count().expect("page_count() should succeed") <= hwm_after_upsert,
1958 "after delete+insert of original size, file must not grow past the upsert transient \
1959 (page_count = {}, transient high-water = {}, original = {}) — the upsert must have \
1960 freed the old chain so the re-insert reuses freed pages",
1961 pager.page_count().expect("page_count() should succeed"),
1962 hwm_after_upsert,
1963 hwm_after_big,
1964 );
1965 assert_eq!(
1966 tree.get(b"k").expect("get() should succeed").as_deref(),
1967 Some(again.as_slice())
1968 );
1969 }
1970 cleanup(&path);
1971 }
1972
1973 #[test]
1976 fn insert_delete_reinsert_does_not_grow_file() {
1977 let path = temp_db_path("reinsert_no_grow");
1978 cleanup(&path);
1979 {
1980 let pager =
1981 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1982 let tree = BTree::new(pager.clone());
1983
1984 let v1 = incompressible(0x5EED, 4 * 1024 * 1024);
1985 tree.insert(b"row", &v1).expect("insert() should succeed");
1986 let pages_after_first = pager.page_count().expect("page_count() should succeed");
1987
1988 assert!(tree.delete(b"row").expect("delete() should succeed"));
1989
1990 let v2 = incompressible(0xACE, 4 * 1024 * 1024);
1991 tree.insert(b"row", &v2).expect("insert() should succeed");
1992 let pages_after_second = pager.page_count().expect("page_count() should succeed");
1993
1994 assert_eq!(
1995 pages_after_second, pages_after_first,
1996 "re-inserting at the same size must reuse the freed chain"
1997 );
1998 assert_eq!(
1999 tree.get(b"row").expect("get() should succeed").as_deref(),
2000 Some(v2.as_slice())
2001 );
2002 }
2003 cleanup(&path);
2004 }
2005
2006 #[test]
2011 fn upsert_batch_in_place_shrink_frees_chain() {
2012 let path = temp_db_path("batch_shrink");
2013 cleanup(&path);
2014 {
2015 let pager =
2016 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
2017 let tree = BTree::new(pager.clone());
2018
2019 let big_a = incompressible(0x1111, 2 * 1024 * 1024);
2022 let big_b = incompressible(0x2222, 2 * 1024 * 1024);
2023 tree.insert(b"a", &big_a).expect("insert() should succeed");
2024 tree.insert(b"b", &big_b).expect("insert() should succeed");
2025 let hwm = pager.page_count().expect("page_count() should succeed");
2026
2027 let updates = vec![
2028 (b"a".to_vec(), b"shrunk-a".to_vec()),
2029 (b"b".to_vec(), b"shrunk-b".to_vec()),
2030 ];
2031 tree.upsert_batch_sorted(&updates)
2032 .expect("upsert_batch_sorted() should succeed");
2033 assert_eq!(
2034 tree.get(b"a").expect("get() should succeed").as_deref(),
2035 Some(b"shrunk-a".as_slice())
2036 );
2037 assert_eq!(
2038 tree.get(b"b").expect("get() should succeed").as_deref(),
2039 Some(b"shrunk-b".as_slice())
2040 );
2041
2042 let again_a = incompressible(0xAAAA, 2 * 1024 * 1024);
2045 let again_b = incompressible(0xBBBB, 2 * 1024 * 1024);
2046 tree.insert(b"c", &again_a)
2047 .expect("insert() should succeed");
2048 tree.insert(b"d", &again_b)
2049 .expect("insert() should succeed");
2050 assert!(
2051 pager.page_count().expect("page_count() should succeed") <= hwm,
2052 "batch in-place shrink must free chains — replacement spills should reuse"
2053 );
2054 }
2055 cleanup(&path);
2056 }
2057}