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 _retirement_pin: Arc<()>,
159 leaf_page_id: u32,
161 position: usize,
163 pager: Arc<Pager>,
165 prefetched_next: bool,
169}
170
171impl BTreeCursor {
172 pub fn next(&mut self) -> BTreeResult<Option<(Vec<u8>, Vec<u8>)>> {
174 if self.leaf_page_id == 0 {
175 return Ok(None);
176 }
177
178 let page = self.pager.read_page(self.leaf_page_id)?;
179 if page.page_type()? != PageType::BTreeLeaf {
180 return Err(BTreeError::Corrupted(
181 "cursor reached a non-leaf page".into(),
182 ));
183 }
184 let cell_count = page.cell_count() as usize;
185
186 if !self.prefetched_next && self.position >= cell_count / 2 && cell_count > 0 {
192 let next_leaf = read_next_leaf(&page);
193 if next_leaf != 0 {
194 self.pager.prefetch_hint(next_leaf);
195 }
196 self.prefetched_next = true;
197 }
198
199 if self.position < cell_count {
201 let (key, stored) = read_leaf_cell(&page, self.position)?;
202 let value = value_layout::decode(&self.pager, &stored)?;
203 self.position += 1;
204 return Ok(Some((key, value)));
205 }
206
207 let next_leaf = read_next_leaf(&page);
209 if next_leaf == 0 {
210 self.leaf_page_id = 0;
211 return Ok(None);
212 }
213 let previous_high_key = leaf_high_key(&page)?;
214
215 self.leaf_page_id = next_leaf;
216 self.position = 0;
217 self.prefetched_next = false; let page = self.pager.read_page(self.leaf_page_id)?;
221 if page.page_type()? != PageType::BTreeLeaf {
222 return Err(BTreeError::Corrupted(
223 "leaf chain reached a non-leaf page".into(),
224 ));
225 }
226 if page.cell_count() == 0 {
227 return Ok(None);
228 }
229
230 let (key, stored) = read_leaf_cell(&page, 0)?;
231 if previous_high_key
235 .as_ref()
236 .is_some_and(|previous| key <= *previous)
237 {
238 self.leaf_page_id = 0;
239 return Err(BTreeError::Corrupted(
240 "leaf chain does not advance its key range".into(),
241 ));
242 }
243 let value = value_layout::decode(&self.pager, &stored)?;
244 self.position = 1;
245 Ok(Some((key, value)))
246 }
247
248 pub fn peek(&self) -> BTreeResult<Option<(Vec<u8>, Vec<u8>)>> {
250 if self.leaf_page_id == 0 {
251 return Ok(None);
252 }
253
254 let page = self.pager.read_page(self.leaf_page_id)?;
255 let cell_count = page.cell_count() as usize;
256
257 if self.position >= cell_count {
258 return Ok(None);
259 }
260
261 let (key, stored) = read_leaf_cell(&page, self.position)?;
262 let value = value_layout::decode(&self.pager, &stored)?;
263 Ok(Some((key, value)))
264 }
265}
266
267pub struct BTree {
269 retirement_pin: Arc<()>,
270 pager: Arc<Pager>,
272 root_page_id: RwLock<u32>,
274 rightmost_leaf: RwLock<Option<(u32, Vec<u8>)>>,
283 writer: parking_lot::ReentrantMutex<()>,
291}
292
293#[path = "btree/impl.rs"]
294mod btree_impl;
295
296#[path = "btree/reclaim.rs"]
297mod reclaim;
298
299#[path = "btree/lehman_yao.rs"]
300pub mod lehman_yao;
301
302#[path = "btree/value_layout.rs"]
303pub mod value_layout;
304enum SearchResult {
307 Found(usize),
308 NotFound(usize),
309}
310
311fn search_leaf(page: &Page, key: &[u8]) -> BTreeResult<SearchResult> {
312 let cell_count = page.cell_count() as usize;
313
314 let mut low = 0;
316 let mut high = cell_count;
317
318 while low < high {
319 let mid = (low + high) / 2;
320 let (cell_key, _) = read_leaf_cell(page, mid)?;
321
322 match cell_key.as_slice().cmp(key) {
323 Ordering::Less => low = mid + 1,
324 Ordering::Greater => high = mid,
325 Ordering::Equal => return Ok(SearchResult::Found(mid)),
326 }
327 }
328
329 Ok(SearchResult::NotFound(low))
330}
331
332fn find_child(page: &Page, key: &[u8]) -> BTreeResult<u32> {
333 let cell_count = page.cell_count() as usize;
334
335 for i in 0..cell_count {
337 let (cell_key, child) = read_interior_cell(page, i)?;
338 if key < cell_key.as_slice() {
339 return Ok(child);
340 }
341 }
342
343 Ok(page.right_child())
345}
346
347fn find_first_child(page: &Page) -> BTreeResult<u32> {
348 if page.cell_count() == 0 {
349 return Ok(page.right_child());
350 }
351 let (_, child) = read_interior_cell(page, 0)?;
352 Ok(child)
353}
354
355fn leaf_min_bytes() -> usize {
356 (PAGE_SIZE - LEAF_DATA_OFFSET) * MIN_FILL_FACTOR / 100
357}
358
359fn interior_min_bytes() -> usize {
360 (PAGE_SIZE - INTERIOR_DATA_OFFSET) * MIN_FILL_FACTOR / 100
361}
362
363fn leaf_entry_size(entry: &(Vec<u8>, Vec<u8>)) -> usize {
367 2 + 4 + entry.0.len() + entry.1.len()
368}
369
370fn leaf_entries_size(entries: &[(Vec<u8>, Vec<u8>)]) -> usize {
371 entries.iter().map(leaf_entry_size).sum()
372}
373
374#[inline]
375fn leaf_slot_offset_for(index: usize) -> usize {
376 LEAF_SLOT_ARRAY_OFFSET + index * 2
377}
378
379#[inline]
380fn leaf_read_slot(page: &Page, index: usize) -> BTreeResult<usize> {
381 let data = page.as_bytes();
382 let slot_pos = leaf_slot_offset_for(index);
383 if slot_pos + 2 > PAGE_SIZE {
384 return Err(BTreeError::Corrupted("slot array overflows page".into()));
385 }
386 Ok(u16::from_le_bytes([data[slot_pos], data[slot_pos + 1]]) as usize)
387}
388
389#[inline]
390fn leaf_write_slot(page: &mut Page, index: usize, cell_offset: u16) -> BTreeResult<()> {
391 let data = page.as_bytes_mut();
392 let slot_pos = leaf_slot_offset_for(index);
393 if slot_pos + 2 > PAGE_SIZE {
394 return Err(BTreeError::Corrupted("slot array overflows page".into()));
395 }
396 data[slot_pos..slot_pos + 2].copy_from_slice(&cell_offset.to_le_bytes());
397 Ok(())
398}
399
400#[inline]
401fn leaf_slots_end(page: &Page) -> usize {
402 LEAF_SLOT_ARRAY_OFFSET + (page.cell_count() as usize) * 2
403}
404
405#[inline]
406fn leaf_cells_start(page: &Page) -> usize {
407 let end = page.free_end() as usize;
408 if end == 0 {
409 PAGE_SIZE
410 } else {
411 end
412 }
413}
414
415#[inline]
416fn leaf_free_bytes(page: &Page) -> usize {
417 let slot_end = leaf_slots_end(page);
418 let cells = leaf_cells_start(page);
419 cells.saturating_sub(slot_end)
420}
421
422fn interior_key_size(key: &[u8]) -> usize {
423 2 + key.len() + 4
424}
425
426fn interior_entries_size(keys: &[Vec<u8>]) -> usize {
427 keys.iter().map(|k| interior_key_size(k)).sum()
428}
429
430fn read_leaf_cell(page: &Page, index: usize) -> BTreeResult<(Vec<u8>, Vec<u8>)> {
436 let cell_count = page.cell_count() as usize;
437 if index >= cell_count {
438 return Err(BTreeError::Corrupted("Cell index out of range".into()));
439 }
440 let offset = leaf_read_slot(page, index)?;
441 let data = page.as_bytes();
442 if offset + 4 > PAGE_SIZE {
443 return Err(BTreeError::Corrupted("cell header out of range".into()));
444 }
445 let key_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
446 let val_len = u16::from_le_bytes([data[offset + 2], data[offset + 3]]) as usize;
447 let end = offset + 4 + key_len + val_len;
448 if end > PAGE_SIZE {
449 return Err(BTreeError::Corrupted("cell body out of range".into()));
450 }
451 let key = data[offset + 4..offset + 4 + key_len].to_vec();
452 let value = data[offset + 4 + key_len..end].to_vec();
453 Ok((key, value))
454}
455
456fn read_leaf_entries(page: &Page) -> BTreeResult<Vec<(Vec<u8>, Vec<u8>)>> {
457 let cell_count = page.cell_count() as usize;
458 let mut entries = Vec::with_capacity(cell_count);
459 for i in 0..cell_count {
460 entries.push(read_leaf_cell(page, i)?);
461 }
462 Ok(entries)
463}
464
465fn write_leaf_entries(page: &mut Page, entries: &[(Vec<u8>, Vec<u8>)]) -> BTreeResult<()> {
471 clear_leaf_cells(page);
472 for (i, (k, v)) in entries.iter().enumerate() {
473 let cell_size = 4 + k.len() + v.len();
474 let cells_start = leaf_cells_start(page);
475 let slot_end = LEAF_SLOT_ARRAY_OFFSET + (i + 1) * 2;
476 if slot_end + cell_size > cells_start {
477 return Err(BTreeError::Corrupted("leaf rebuild overflowed page".into()));
478 }
479 let cell_offset = cells_start - cell_size;
480 {
481 let data = page.as_bytes_mut();
482 data[cell_offset..cell_offset + 2].copy_from_slice(&(k.len() as u16).to_le_bytes());
483 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&(v.len() as u16).to_le_bytes());
484 data[cell_offset + 4..cell_offset + 4 + k.len()].copy_from_slice(k);
485 data[cell_offset + 4 + k.len()..cell_offset + 4 + k.len() + v.len()].copy_from_slice(v);
486 }
487 page.set_free_end(cell_offset as u16);
488 leaf_write_slot(page, i, cell_offset as u16)?;
489 }
490 page.set_cell_count(entries.len() as u16);
491 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + entries.len() * 2) as u16);
492 Ok(())
493}
494
495fn can_insert_leaf(page: &Page, key: &[u8], value: &[u8]) -> bool {
496 let needed = 2 + 4 + key.len() + value.len();
497 leaf_free_bytes(page) >= needed
498}
499
500pub(crate) fn overwrite_leaf_value(
516 page: &mut Page,
517 index: usize,
518 new_value: &[u8],
519) -> BTreeResult<()> {
520 let cell_offset = leaf_read_slot(page, index)?;
521 let data = page.as_bytes();
522 if cell_offset + 4 > data.len() {
523 return Err(BTreeError::Corrupted(
524 "leaf cell header out of bounds".into(),
525 ));
526 }
527 let k_len = u16::from_le_bytes([data[cell_offset], data[cell_offset + 1]]) as usize;
528 let old_v_len = u16::from_le_bytes([data[cell_offset + 2], data[cell_offset + 3]]) as usize;
529 if new_value.len() > old_v_len {
530 return Err(BTreeError::Corrupted(
531 "overwrite_leaf_value: new value larger than slot".into(),
532 ));
533 }
534 let value_start = cell_offset + 4 + k_len;
535 if value_start + new_value.len() > data.len() {
536 return Err(BTreeError::Corrupted(
537 "leaf cell value out of bounds".into(),
538 ));
539 }
540 let data = page.as_bytes_mut();
541 if new_value.len() != old_v_len {
544 let new_len = new_value.len() as u16;
545 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&new_len.to_le_bytes());
546 }
547 data[value_start..value_start + new_value.len()].copy_from_slice(new_value);
548 Ok(())
549}
550
551fn insert_into_leaf(page: &mut Page, key: &[u8], value: &[u8]) -> BTreeResult<()> {
553 let cell_count = page.cell_count() as usize;
555 let mut low = 0;
556 let mut high = cell_count;
557 while low < high {
558 let mid = (low + high) / 2;
559 let (cell_key, _) = read_leaf_cell(page, mid)?;
560 match cell_key.as_slice().cmp(key) {
561 Ordering::Less => low = mid + 1,
562 Ordering::Greater => high = mid,
563 Ordering::Equal => {
564 low = mid + 1;
567 break;
568 }
569 }
570 }
571 let insert_pos = low;
572
573 let cell_size = 4 + key.len() + value.len();
575 let slot_end_after = LEAF_SLOT_ARRAY_OFFSET + (cell_count + 1) * 2;
576 let cells_start = leaf_cells_start(page);
577 if slot_end_after + cell_size > cells_start {
578 return Err(BTreeError::Corrupted("leaf page full".into()));
579 }
580 let cell_offset = cells_start - cell_size;
581 {
582 let data = page.as_bytes_mut();
583 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
584 data[cell_offset + 2..cell_offset + 4].copy_from_slice(&(value.len() as u16).to_le_bytes());
585 data[cell_offset + 4..cell_offset + 4 + key.len()].copy_from_slice(key);
586 data[cell_offset + 4 + key.len()..cell_offset + 4 + key.len() + value.len()]
587 .copy_from_slice(value);
588 }
589 page.set_free_end(cell_offset as u16);
590
591 {
595 let shift_from = leaf_slot_offset_for(insert_pos);
596 let shift_to = shift_from + 2;
597 let shift_len = (cell_count - insert_pos) * 2;
598 if shift_len > 0 {
599 let data = page.as_bytes_mut();
600 data.copy_within(shift_from..shift_from + shift_len, shift_to);
601 }
602 }
603 leaf_write_slot(page, insert_pos, cell_offset as u16)?;
604
605 page.set_cell_count((cell_count + 1) as u16);
607 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + (cell_count + 1) * 2) as u16);
608 Ok(())
609}
610
611fn delete_from_leaf(page: &mut Page, index: usize) -> BTreeResult<()> {
616 let cell_count = page.cell_count() as usize;
617 if index >= cell_count {
618 return Err(BTreeError::Corrupted("delete index out of range".into()));
619 }
620 if index + 1 < cell_count {
621 let shift_from = leaf_slot_offset_for(index + 1);
622 let shift_to = leaf_slot_offset_for(index);
623 let shift_len = (cell_count - index - 1) * 2;
624 let data = page.as_bytes_mut();
625 data.copy_within(shift_from..shift_from + shift_len, shift_to);
626 }
627 page.set_cell_count((cell_count - 1) as u16);
628 page.set_free_start((LEAF_SLOT_ARRAY_OFFSET + (cell_count - 1) * 2) as u16);
629 Ok(())
630}
631
632fn clear_leaf_cells(page: &mut Page) {
636 {
637 let data = page.as_bytes_mut();
638 for byte in &mut data[LEAF_SLOT_ARRAY_OFFSET..] {
639 *byte = 0;
640 }
641 }
642 page.set_cell_count(0);
643 page.set_free_start(LEAF_SLOT_ARRAY_OFFSET as u16);
644 page.set_free_end(PAGE_SIZE as u16);
645}
646
647fn init_leaf_links(page: &mut Page, prev: u32, next: u32) {
648 let data = page.as_bytes_mut();
649 data[LEAF_PREV_OFFSET..LEAF_PREV_OFFSET + 4].copy_from_slice(&prev.to_le_bytes());
650 data[LEAF_NEXT_OFFSET..LEAF_NEXT_OFFSET + 4].copy_from_slice(&next.to_le_bytes());
651}
652
653fn read_next_leaf(page: &Page) -> u32 {
654 let data = page.as_bytes();
655 u32::from_le_bytes([
656 data[LEAF_NEXT_OFFSET],
657 data[LEAF_NEXT_OFFSET + 1],
658 data[LEAF_NEXT_OFFSET + 2],
659 data[LEAF_NEXT_OFFSET + 3],
660 ])
661}
662
663#[inline]
666fn leaf_right_sibling(page: &Page) -> u32 {
667 read_next_leaf(page)
668}
669
670fn leaf_high_key(page: &Page) -> BTreeResult<Option<Vec<u8>>> {
674 let cell_count = page.cell_count() as usize;
675 if cell_count == 0 {
676 return Ok(None);
677 }
678 let (key, _) = read_leaf_cell(page, cell_count - 1)?;
679 Ok(Some(key))
680}
681
682fn set_prev_leaf(page: &mut Page, prev: u32) {
683 let data = page.as_bytes_mut();
684 data[LEAF_PREV_OFFSET..LEAF_PREV_OFFSET + 4].copy_from_slice(&prev.to_le_bytes());
685}
686
687fn set_next_leaf(page: &mut Page, next: u32) {
688 let data = page.as_bytes_mut();
689 data[LEAF_NEXT_OFFSET..LEAF_NEXT_OFFSET + 4].copy_from_slice(&next.to_le_bytes());
690}
691
692#[inline]
707fn interior_slot_offset_for(index: usize) -> usize {
708 INTERIOR_DATA_OFFSET + index * 2
709}
710
711#[inline]
712fn interior_read_slot(page: &Page, index: usize) -> BTreeResult<usize> {
713 let data = page.as_bytes();
714 let slot_pos = interior_slot_offset_for(index);
715 if slot_pos + 2 > PAGE_SIZE {
716 return Err(BTreeError::Corrupted(
717 "interior slot array overflows page".into(),
718 ));
719 }
720 Ok(u16::from_le_bytes([data[slot_pos], data[slot_pos + 1]]) as usize)
721}
722
723#[inline]
724fn interior_write_slot(page: &mut Page, index: usize, cell_offset: u16) -> BTreeResult<()> {
725 let data = page.as_bytes_mut();
726 let slot_pos = interior_slot_offset_for(index);
727 if slot_pos + 2 > PAGE_SIZE {
728 return Err(BTreeError::Corrupted(
729 "interior slot array overflows page".into(),
730 ));
731 }
732 data[slot_pos..slot_pos + 2].copy_from_slice(&cell_offset.to_le_bytes());
733 Ok(())
734}
735
736#[inline]
737fn interior_cells_start(page: &Page) -> usize {
738 let end = page.free_end() as usize;
739 if end == 0 {
740 PAGE_SIZE
741 } else {
742 end
743 }
744}
745
746#[inline]
747fn interior_free_bytes(page: &Page) -> usize {
748 let slot_end = INTERIOR_DATA_OFFSET + (page.cell_count() as usize) * 2;
749 interior_cells_start(page).saturating_sub(slot_end)
750}
751
752fn read_interior_cell(page: &Page, index: usize) -> BTreeResult<(Vec<u8>, u32)> {
753 let cell_count = page.cell_count() as usize;
754 if index >= cell_count {
755 return Err(BTreeError::Corrupted("Cell index out of range".into()));
756 }
757 let offset = interior_read_slot(page, index)?;
758 let data = page.as_bytes();
759 if offset + 2 > PAGE_SIZE {
760 return Err(BTreeError::Corrupted(
761 "interior cell header out of range".into(),
762 ));
763 }
764 let key_len = u16::from_le_bytes([data[offset], data[offset + 1]]) as usize;
765 let end = offset + 2 + key_len + 4;
766 if end > PAGE_SIZE {
767 return Err(BTreeError::Corrupted(
768 "interior cell body out of range".into(),
769 ));
770 }
771 let key = data[offset + 2..offset + 2 + key_len].to_vec();
772 let child = u32::from_le_bytes([
773 data[offset + 2 + key_len],
774 data[offset + 2 + key_len + 1],
775 data[offset + 2 + key_len + 2],
776 data[offset + 2 + key_len + 3],
777 ]);
778 Ok((key, child))
779}
780
781fn read_interior_keys_children(page: &Page) -> BTreeResult<(Vec<Vec<u8>>, Vec<u32>)> {
782 let cell_count = page.cell_count() as usize;
783 let mut keys = Vec::with_capacity(cell_count);
784 let mut children = Vec::with_capacity(cell_count + 1);
785
786 for i in 0..cell_count {
787 let (key, child) = read_interior_cell(page, i)?;
788 keys.push(key);
789 children.push(child);
790 }
791
792 if cell_count == 0 {
793 let right_child = page.right_child();
794 if right_child != 0 {
795 children.push(right_child);
796 }
797 } else {
798 children.push(page.right_child());
799 }
800
801 Ok((keys, children))
802}
803
804fn write_interior_entries(page: &mut Page, keys: &[Vec<u8>], children: &[u32]) -> BTreeResult<()> {
809 if !keys.is_empty() && children.len() != keys.len() + 1 {
810 return Err(BTreeError::Corrupted(
811 "Interior keys/children length mismatch".into(),
812 ));
813 }
814
815 clear_interior_cells(page);
816 if keys.is_empty() {
817 let right_child = children.first().copied().unwrap_or(0);
818 page.set_right_child(right_child);
819 return Ok(());
820 }
821
822 for (i, key) in keys.iter().enumerate() {
823 let cell_size = 2 + key.len() + 4;
824 let cells_start = interior_cells_start(page);
825 let slot_end = INTERIOR_DATA_OFFSET + (i + 1) * 2;
826 if slot_end + cell_size > cells_start {
827 return Err(BTreeError::Corrupted(
828 "interior rebuild overflowed page".into(),
829 ));
830 }
831 let cell_offset = cells_start - cell_size;
832 {
833 let data = page.as_bytes_mut();
834 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
835 data[cell_offset + 2..cell_offset + 2 + key.len()].copy_from_slice(key);
836 data[cell_offset + 2 + key.len()..cell_offset + 2 + key.len() + 4]
837 .copy_from_slice(&children[i].to_le_bytes());
838 }
839 page.set_free_end(cell_offset as u16);
840 interior_write_slot(page, i, cell_offset as u16)?;
841 }
842 page.set_cell_count(keys.len() as u16);
843 page.set_free_start((INTERIOR_DATA_OFFSET + keys.len() * 2) as u16);
844 page.set_right_child(*children.last().ok_or_else(|| {
845 BTreeError::Corrupted("write_interior_entries: children empty with non-empty keys".into())
846 })?);
847 Ok(())
848}
849
850fn can_insert_interior(page: &Page, key: &[u8]) -> bool {
851 let needed = 2 + 2 + key.len() + 4;
852 interior_free_bytes(page) >= needed
853}
854
855fn insert_into_interior(
860 page: &mut Page,
861 key: &[u8],
862 left_child: u32,
863 right_child: u32,
864) -> BTreeResult<()> {
865 let cell_count = page.cell_count() as usize;
867 let mut low = 0;
868 let mut high = cell_count;
869 while low < high {
870 let mid = (low + high) / 2;
871 let (cell_key, _) = read_interior_cell(page, mid)?;
872 match cell_key.as_slice().cmp(key) {
873 Ordering::Less => low = mid + 1,
874 Ordering::Greater | Ordering::Equal => high = mid,
875 }
876 }
877 let insert_pos = low;
878
879 if insert_pos < cell_count {
884 let old_offset = interior_read_slot(page, insert_pos)?;
885 let data = page.as_bytes();
886 let key_len = u16::from_le_bytes([data[old_offset], data[old_offset + 1]]) as usize;
887 let child_pos = old_offset + 2 + key_len;
888 let data = page.as_bytes_mut();
889 data[child_pos..child_pos + 4].copy_from_slice(&right_child.to_le_bytes());
890 } else {
891 page.set_right_child(right_child);
892 }
893
894 let cell_size = 2 + key.len() + 4;
896 let slot_end_after = INTERIOR_DATA_OFFSET + (cell_count + 1) * 2;
897 let cells_start = interior_cells_start(page);
898 if slot_end_after + cell_size > cells_start {
899 return Err(BTreeError::Corrupted("interior page full".into()));
900 }
901 let cell_offset = cells_start - cell_size;
902 {
903 let data = page.as_bytes_mut();
904 data[cell_offset..cell_offset + 2].copy_from_slice(&(key.len() as u16).to_le_bytes());
905 data[cell_offset + 2..cell_offset + 2 + key.len()].copy_from_slice(key);
906 data[cell_offset + 2 + key.len()..cell_offset + 2 + key.len() + 4]
907 .copy_from_slice(&left_child.to_le_bytes());
908 }
909 page.set_free_end(cell_offset as u16);
910
911 {
914 let shift_from = interior_slot_offset_for(insert_pos);
915 let shift_to = shift_from + 2;
916 let shift_len = (cell_count - insert_pos) * 2;
917 if shift_len > 0 {
918 let data = page.as_bytes_mut();
919 data.copy_within(shift_from..shift_from + shift_len, shift_to);
920 }
921 }
922 interior_write_slot(page, insert_pos, cell_offset as u16)?;
923 page.set_cell_count((cell_count + 1) as u16);
924 page.set_free_start((INTERIOR_DATA_OFFSET + (cell_count + 1) * 2) as u16);
925 Ok(())
926}
927
928fn clear_interior_cells(page: &mut Page) {
929 {
930 let data = page.as_bytes_mut();
931 for byte in &mut data[INTERIOR_DATA_OFFSET..] {
932 *byte = 0;
933 }
934 }
935 page.set_cell_count(0);
936 page.set_free_start(INTERIOR_DATA_OFFSET as u16);
937 page.set_free_end(PAGE_SIZE as u16);
938}
939
940#[cfg(test)]
941mod tests {
942 use super::*;
943 use std::path::PathBuf;
944
945 struct TempDbPath(PathBuf);
952 impl std::ops::Deref for TempDbPath {
953 type Target = PathBuf;
954 fn deref(&self) -> &PathBuf {
955 &self.0
956 }
957 }
958 impl AsRef<std::path::Path> for TempDbPath {
959 fn as_ref(&self) -> &std::path::Path {
960 self.0.as_ref()
961 }
962 }
963 impl Drop for TempDbPath {
964 fn drop(&mut self) {
965 cleanup(&self.0);
966 }
967 }
968
969 fn temp_db_path() -> TempDbPath {
970 use std::sync::atomic::{AtomicU64, Ordering};
971 static COUNTER: AtomicU64 = AtomicU64::new(0);
972 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
973 let mut path = std::env::temp_dir();
974 path.push(format!("reddb_btree_test_{}_{}.db", std::process::id(), id));
975 TempDbPath(path)
976 }
977
978 fn cleanup(path: &PathBuf) {
979 if let (Some(dir), Some(name)) = (
983 path.parent(),
984 path.file_name().and_then(|name| name.to_str()),
985 ) {
986 if let Ok(entries) = std::fs::read_dir(dir) {
987 for entry in entries.flatten() {
988 if entry
989 .file_name()
990 .to_str()
991 .is_some_and(|entry_name| entry_name.starts_with(name))
992 {
993 let _ = std::fs::remove_file(entry.path());
994 }
995 }
996 }
997 }
998 }
999
1000 #[test]
1001 fn test_btree_empty() {
1002 let path = temp_db_path();
1003 cleanup(&path);
1004
1005 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1006 let tree = BTree::new(pager);
1007
1008 assert!(tree.is_empty());
1009 assert_eq!(tree.root_page_id(), 0);
1010 assert_eq!(tree.get(b"key").expect("get() should succeed"), None);
1011 assert_eq!(tree.count().expect("count() should succeed"), 0);
1012
1013 cleanup(&path);
1014 }
1015
1016 #[test]
1017 fn test_btree_single_insert() {
1018 let path = temp_db_path();
1019 cleanup(&path);
1020
1021 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1022 let tree = BTree::new(pager);
1023
1024 tree.insert(b"hello", b"world")
1025 .expect("insert() should succeed");
1026
1027 assert!(!tree.is_empty());
1028 assert_eq!(
1029 tree.get(b"hello").expect("get() should succeed"),
1030 Some(b"world".to_vec())
1031 );
1032 assert_eq!(tree.get(b"other").expect("get() should succeed"), None);
1033 assert_eq!(tree.count().expect("count() should succeed"), 1);
1034
1035 cleanup(&path);
1036 }
1037
1038 #[test]
1039 fn test_btree_multiple_inserts() {
1040 let path = temp_db_path();
1041 cleanup(&path);
1042
1043 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1044 let tree = BTree::new(pager);
1045
1046 tree.insert(b"c", b"3").expect("insert() should succeed");
1047 tree.insert(b"a", b"1").expect("insert() should succeed");
1048 tree.insert(b"b", b"2").expect("insert() should succeed");
1049
1050 assert_eq!(
1051 tree.get(b"a").expect("get() should succeed"),
1052 Some(b"1".to_vec())
1053 );
1054 assert_eq!(
1055 tree.get(b"b").expect("get() should succeed"),
1056 Some(b"2".to_vec())
1057 );
1058 assert_eq!(
1059 tree.get(b"c").expect("get() should succeed"),
1060 Some(b"3".to_vec())
1061 );
1062 assert_eq!(tree.count().expect("count() should succeed"), 3);
1063
1064 cleanup(&path);
1065 }
1066
1067 #[test]
1068 fn test_btree_duplicate_key() {
1069 let path = temp_db_path();
1070 cleanup(&path);
1071
1072 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1073 let tree = BTree::new(pager);
1074
1075 tree.insert(b"key", b"value1")
1076 .expect("insert() should succeed");
1077 let result = tree.insert(b"key", b"value2");
1078
1079 assert!(matches!(result, Err(BTreeError::DuplicateKey)));
1080 assert_eq!(
1081 tree.get(b"key").expect("get() should succeed"),
1082 Some(b"value1".to_vec())
1083 );
1084
1085 cleanup(&path);
1086 }
1087
1088 #[test]
1089 fn test_btree_delete() {
1090 let path = temp_db_path();
1091 cleanup(&path);
1092
1093 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1094 let tree = BTree::new(pager);
1095
1096 tree.insert(b"a", b"1").expect("insert() should succeed");
1097 tree.insert(b"b", b"2").expect("insert() should succeed");
1098 tree.insert(b"c", b"3").expect("insert() should succeed");
1099
1100 assert!(tree.delete(b"b").expect("delete() should succeed"));
1101 assert!(!tree.delete(b"d").expect("delete() should succeed"));
1102
1103 assert_eq!(
1104 tree.get(b"a").expect("get() should succeed"),
1105 Some(b"1".to_vec())
1106 );
1107 assert_eq!(tree.get(b"b").expect("get() should succeed"), None);
1108 assert_eq!(
1109 tree.get(b"c").expect("get() should succeed"),
1110 Some(b"3".to_vec())
1111 );
1112 assert_eq!(tree.count().expect("count() should succeed"), 2);
1113
1114 cleanup(&path);
1115 }
1116
1117 #[test]
1118 fn test_btree_delete_rebalance_removes_empty_leaf() {
1119 let path = temp_db_path();
1120 cleanup(&path);
1121
1122 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1123 let tree = BTree::new(pager.clone());
1124
1125 let value = vec![b'v'; 200];
1126 for i in 0..300u32 {
1127 let key = format!("key{:03}", i);
1128 tree.insert(key.as_bytes(), &value)
1129 .expect("insert() should succeed");
1130 }
1131
1132 let root_id = tree.root_page_id();
1133 let first_leaf = tree
1134 .find_first_leaf(root_id)
1135 .expect("find_first_leaf() should succeed");
1136 let mut leaf_ids = Vec::new();
1137 let mut current = first_leaf;
1138 loop {
1139 leaf_ids.push(current);
1140 let page = pager
1141 .read_page(current)
1142 .expect("read_page() should succeed");
1143 let next = read_next_leaf(&page);
1144 if next == 0 {
1145 break;
1146 }
1147 current = next;
1148 }
1149
1150 assert!(leaf_ids.len() >= 3);
1151
1152 let target_leaf = leaf_ids[1];
1153 let page = pager
1154 .read_page(target_leaf)
1155 .expect("read_page() should succeed");
1156 let cell_count = page.cell_count() as usize;
1157 let mut keys = Vec::with_capacity(cell_count);
1158 for i in 0..cell_count {
1159 let (key, _) = read_leaf_cell(&page, i).expect("read_leaf_cell() should succeed");
1160 keys.push(key);
1161 }
1162
1163 for key in &keys {
1164 tree.delete(key).expect("delete() should succeed");
1165 }
1166
1167 let expected = 300 - keys.len();
1168 assert_eq!(tree.count().expect("count() should succeed"), expected);
1169
1170 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1171 let mut results = Vec::new();
1172 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1173 results.push(key);
1174 }
1175
1176 assert_eq!(results.len(), expected);
1177 let last_key = format!("key{:03}", 299).into_bytes();
1178 assert_eq!(results.last(), Some(&last_key));
1179
1180 cleanup(&path);
1181 }
1182
1183 #[test]
1184 fn test_btree_cursor() {
1185 let path = temp_db_path();
1186 cleanup(&path);
1187
1188 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1189 let tree = BTree::new(pager);
1190
1191 tree.insert(b"c", b"3").expect("insert() should succeed");
1192 tree.insert(b"a", b"1").expect("insert() should succeed");
1193 tree.insert(b"b", b"2").expect("insert() should succeed");
1194
1195 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1196 let mut results: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
1197
1198 while let Some(entry) = cursor.next().expect("next() should succeed") {
1199 results.push(entry);
1200 }
1201
1202 assert_eq!(results.len(), 3);
1203 assert_eq!(results[0], (b"a".to_vec(), b"1".to_vec()));
1204 assert_eq!(results[1], (b"b".to_vec(), b"2".to_vec()));
1205 assert_eq!(results[2], (b"c".to_vec(), b"3".to_vec()));
1206
1207 cleanup(&path);
1208 }
1209
1210 #[test]
1211 fn test_btree_range() {
1212 let path = temp_db_path();
1213 cleanup(&path);
1214
1215 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1216 let tree = BTree::new(pager);
1217
1218 for i in 0..10u8 {
1219 let key = format!("key{:02}", i);
1220 let value = format!("val{:02}", i);
1221 tree.insert(key.as_bytes(), value.as_bytes())
1222 .expect("insert() should succeed");
1223 }
1224
1225 let results = tree
1226 .range(b"key03", b"key06")
1227 .expect("range() should succeed");
1228
1229 assert_eq!(results.len(), 4);
1230 assert_eq!(results[0].0, b"key03".to_vec());
1231 assert_eq!(results[3].0, b"key06".to_vec());
1232
1233 cleanup(&path);
1234 }
1235
1236 #[test]
1237 fn test_btree_large_keys() {
1238 let path = temp_db_path();
1239 cleanup(&path);
1240
1241 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1242 let tree = BTree::new(pager);
1243
1244 let key = vec![b'x'; 500];
1245 let value = vec![b'y'; 500];
1246
1247 tree.insert(&key, &value).expect("insert() should succeed");
1248 assert_eq!(tree.get(&key).expect("get() should succeed"), Some(value));
1249
1250 cleanup(&path);
1251 }
1252
1253 #[test]
1254 fn test_btree_key_too_large() {
1255 let path = temp_db_path();
1256 cleanup(&path);
1257
1258 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1259 let tree = BTree::new(pager);
1260
1261 let key = vec![b'x'; MAX_KEY_SIZE + 1];
1262 let result = tree.insert(&key, b"value");
1263
1264 assert!(matches!(result, Err(BTreeError::KeyTooLarge(_))));
1265
1266 cleanup(&path);
1267 }
1268
1269 #[test]
1270 fn test_btree_many_inserts() {
1271 let path = temp_db_path();
1272 cleanup(&path);
1273
1274 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1275 let tree = BTree::new(pager);
1276
1277 for i in 0..100u32 {
1279 let key = format!("key{:08}", i);
1280 let value = format!("value{:08}", i);
1281 tree.insert(key.as_bytes(), value.as_bytes())
1282 .expect("insert() should succeed");
1283 }
1284
1285 for i in 0..100u32 {
1287 let key = format!("key{:08}", i);
1288 let expected = format!("value{:08}", i);
1289 assert_eq!(
1290 tree.get(key.as_bytes()).expect("get() should succeed"),
1291 Some(expected.into_bytes())
1292 );
1293 }
1294
1295 assert_eq!(tree.count().expect("count() should succeed"), 100);
1296
1297 cleanup(&path);
1298 }
1299
1300 #[test]
1301 fn test_btree_bulk_insert_sorted_preserves_leaf_layout() {
1302 let path = temp_db_path();
1303 cleanup(&path);
1304
1305 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1306 let tree = BTree::new(pager);
1307
1308 let items: Vec<(Vec<u8>, Vec<u8>)> = (0..240u32)
1309 .map(|i| {
1310 (
1311 format!("key{:08}", i).into_bytes(),
1312 vec![b'v'; 32 + (i as usize % 7)],
1313 )
1314 })
1315 .collect();
1316
1317 tree.bulk_insert_sorted(&items)
1318 .expect("bulk_insert_sorted() should succeed");
1319
1320 assert_eq!(tree.count().expect("count() should succeed"), items.len());
1321
1322 for (key, value) in &items {
1323 assert_eq!(
1324 tree.get(key).expect("get() should succeed"),
1325 Some(value.clone())
1326 );
1327 }
1328
1329 cleanup(&path);
1330 }
1331
1332 mod prop_upsert_batch_sorted {
1342 use super::*;
1343 use proptest::prelude::*;
1344 use std::collections::BTreeMap;
1345
1346 fn populate_tree(seed: &[(Vec<u8>, Vec<u8>)]) -> (BTree, TempDbPath, Arc<Pager>) {
1349 let path = temp_db_path();
1350 cleanup(&path);
1351 let pager =
1352 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1353 let tree = BTree::new(Arc::clone(&pager));
1354 for (k, v) in seed {
1355 tree.upsert(k, v).expect("upsert() should succeed");
1356 }
1357 (tree, path, pager)
1358 }
1359
1360 fn key_strategy() -> impl Strategy<Value = Vec<u8>> {
1361 prop::collection::vec(0u8..16u8, 1..=4)
1365 }
1366
1367 fn value_strategy() -> impl Strategy<Value = Vec<u8>> {
1368 prop::collection::vec(0u8..255u8, 0..=8)
1372 }
1373
1374 fn pair_strategy() -> impl Strategy<Value = (Vec<u8>, Vec<u8>)> {
1375 (key_strategy(), value_strategy())
1376 }
1377
1378 proptest! {
1379 #![proptest_config(ProptestConfig {
1380 cases: 64,
1381 .. ProptestConfig::default()
1382 })]
1383
1384 #[test]
1385 fn batch_upsert_matches_loop_upsert(
1386 seed in prop::collection::vec(pair_strategy(), 0..50),
1387 batch in prop::collection::vec(pair_strategy(), 1..200),
1388 ) {
1389 let mut seed_map: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
1392 for (k, v) in seed {
1393 seed_map.insert(k, v);
1394 }
1395 let seed: Vec<(Vec<u8>, Vec<u8>)> = seed_map.into_iter().collect();
1396
1397 let (baseline, baseline_path, _baseline_pager) = populate_tree(&seed);
1399 for (k, v) in &batch {
1400 baseline.upsert(k, v).expect("upsert() should succeed");
1401 }
1402
1403 let (candidate, candidate_path, _candidate_pager) = populate_tree(&seed);
1406 let mut sorted = batch.clone();
1407 sorted.sort_by(|a, b| a.0.cmp(&b.0));
1408 candidate.upsert_batch_sorted(&sorted).expect("upsert_batch_sorted() should succeed");
1409
1410 let mut expected: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
1413 for (k, v) in &seed {
1414 expected.insert(k.clone(), v.clone());
1415 }
1416 for (k, v) in &batch {
1417 expected.insert(k.clone(), v.clone());
1418 }
1419
1420 for (k, v) in &expected {
1422 let baseline_got = baseline.get(k).expect("get() should succeed");
1423 let candidate_got = candidate.get(k).expect("get() should succeed");
1424 prop_assert_eq!(baseline_got.as_ref(), Some(v));
1425 prop_assert_eq!(candidate_got.as_ref(), Some(v));
1426 }
1427
1428 let collect = |t: &BTree| -> Vec<(Vec<u8>, Vec<u8>)> {
1430 let mut out = Vec::new();
1431 let mut cur = t.cursor_first().expect("cursor_first() should succeed");
1432 while let Some(entry) = cur.next().expect("next() should succeed") {
1433 out.push(entry);
1434 }
1435 out
1436 };
1437 let baseline_contents = collect(&baseline);
1438 let candidate_contents = collect(&candidate);
1439 prop_assert_eq!(&baseline_contents, &candidate_contents);
1440
1441 let expected_contents: Vec<(Vec<u8>, Vec<u8>)> =
1443 expected.into_iter().collect();
1444 prop_assert_eq!(&candidate_contents, &expected_contents);
1445
1446 drop(baseline);
1454 drop(candidate);
1455 drop(_baseline_pager);
1456 drop(_candidate_pager);
1457 cleanup(&baseline_path);
1458 cleanup(&candidate_path);
1459 }
1460 }
1461 }
1462
1463 #[test]
1464 fn test_btree_sorted_iteration() {
1465 let path = temp_db_path();
1466 cleanup(&path);
1467
1468 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1469 let tree = BTree::new(pager);
1470
1471 let keys = vec![50, 25, 75, 10, 30, 60, 80, 5, 15, 27, 35, 55, 65, 77, 90];
1473 for k in &keys {
1474 let key = format!("{:03}", k);
1475 tree.insert(key.as_bytes(), key.as_bytes())
1476 .expect("insert() should succeed");
1477 }
1478
1479 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1481 let mut prev: Option<Vec<u8>> = None;
1482
1483 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1484 if let Some(p) = &prev {
1485 assert!(p < &key, "Keys not in sorted order");
1486 }
1487 prev = Some(key);
1488 }
1489
1490 cleanup(&path);
1491 }
1492
1493 #[test]
1498 fn concurrent_writers_on_one_tree_keep_every_key() {
1499 const WRITER_COUNT: u32 = 4;
1500 const KEY_COUNT_PER_WRITER: u32 = 2_000;
1501
1502 let path = temp_db_path();
1503 cleanup(&path);
1504 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1505 let tree = Arc::new(BTree::new(pager));
1506 let value = vec![7u8; 96];
1507
1508 let writers: Vec<_> = (0..WRITER_COUNT)
1509 .map(|writer| {
1510 let tree = Arc::clone(&tree);
1511 let value = value.clone();
1512 std::thread::spawn(move || {
1513 for i in 0..KEY_COUNT_PER_WRITER {
1514 let key = (i * WRITER_COUNT + writer).to_be_bytes();
1516 tree.insert(&key, &value).expect("insert() should succeed");
1517 if i % 4 == 0 {
1519 assert!(tree.delete(&key).expect("delete() should succeed"));
1520 tree.upsert(&key, &value).expect("upsert() should succeed");
1521 }
1522 }
1523 })
1524 })
1525 .collect();
1526 for writer in writers {
1527 writer.join().expect("writer thread panicked");
1528 }
1529
1530 let key_count = WRITER_COUNT * KEY_COUNT_PER_WRITER;
1531 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
1532 let mut seen = Vec::new();
1533 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
1534 seen.push(u32::from_be_bytes(
1535 key.as_slice().try_into().expect("invariant: 4-byte keys"),
1536 ));
1537 }
1538 let expected: Vec<u32> = (0..key_count).collect();
1539 assert_eq!(seen, expected, "every key exactly once, in order");
1540 for key in 0..key_count {
1541 assert!(
1542 tree.get(&key.to_be_bytes())
1543 .expect("get() should succeed")
1544 .is_some(),
1545 "key {key} must be reachable from the root"
1546 );
1547 }
1548
1549 cleanup(&path);
1550 }
1551}
1552
1553#[cfg(test)]
1558mod overflow_pipeline_tests {
1559 use super::value_layout::{MAX_VALUE_SIZE, OVERFLOW_THRESHOLD};
1560 use super::*;
1561 use std::path::PathBuf;
1562
1563 fn temp_db_path(tag: &str) -> PathBuf {
1564 use std::sync::atomic::{AtomicU64, Ordering};
1565 static COUNTER: AtomicU64 = AtomicU64::new(0);
1566 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
1567 let mut path = std::env::temp_dir();
1568 path.push(format!(
1569 "reddb_btree_spill_{}_{}_{}.db",
1570 tag,
1571 std::process::id(),
1572 id
1573 ));
1574 path
1575 }
1576
1577 fn cleanup(path: &std::path::Path) {
1578 let _ = std::fs::remove_file(path);
1579 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
1580 let _ = std::fs::remove_file(sidecar);
1581 }
1582 }
1583
1584 #[test]
1589 fn inline_path_allocates_no_overflow_pages() {
1590 let path = temp_db_path("inline_no_overflow");
1591 cleanup(&path);
1592 {
1593 let pager =
1594 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1595 let tree = BTree::new(pager.clone());
1596 let before = pager.page_count().expect("page_count() should succeed");
1597
1598 let value = vec![0x5Au8; OVERFLOW_THRESHOLD - 1];
1599 tree.insert(b"k", &value).expect("insert() should succeed");
1600
1601 let after = pager.page_count().expect("page_count() should succeed");
1602 assert_eq!(
1605 after - before,
1606 1,
1607 "inline value must allocate only the root leaf"
1608 );
1609 assert_eq!(tree.get(b"k").expect("get() should succeed"), Some(value));
1610 }
1611 cleanup(&path);
1612 }
1613
1614 #[test]
1618 fn compressible_44kb_round_trips() {
1619 let path = temp_db_path("compressible_44kb");
1620 cleanup(&path);
1621 {
1622 let pager =
1623 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1624 let tree = BTree::new(pager);
1625
1626 let snippet = "# heading\n- bullet line that says the same thing over and over\n";
1628 let mut value = Vec::with_capacity(44 * 1024);
1629 while value.len() < 44 * 1024 {
1630 value.extend_from_slice(snippet.as_bytes());
1631 }
1632 assert!(value.len() > OVERFLOW_THRESHOLD);
1633
1634 tree.insert(b"doc", &value)
1635 .expect("insert() should succeed");
1636 assert_eq!(tree.get(b"doc").expect("get() should succeed"), Some(value));
1637 }
1638 cleanup(&path);
1639 }
1640
1641 #[test]
1644 fn incompressible_5mb_spills_and_round_trips() {
1645 let path = temp_db_path("incompressible_5mb");
1646 cleanup(&path);
1647 {
1648 let pager =
1649 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1650 let tree = BTree::new(pager.clone());
1651
1652 let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
1655 let value: Vec<u8> = (0..5 * 1024 * 1024)
1656 .map(|_| {
1657 state ^= state << 13;
1658 state ^= state >> 7;
1659 state ^= state << 17;
1660 state as u8
1661 })
1662 .collect();
1663
1664 let before = pager.page_count().expect("page_count() should succeed");
1665 tree.insert(b"blob", &value)
1666 .expect("insert() should succeed");
1667 let after = pager.page_count().expect("page_count() should succeed");
1668 assert!(
1671 after - before > 2,
1672 "5 MB spill must allocate overflow pages (alloc'd {} pages)",
1673 after - before
1674 );
1675
1676 assert_eq!(
1677 tree.get(b"blob").expect("get() should succeed").as_deref(),
1678 Some(value.as_slice())
1679 );
1680 }
1681 cleanup(&path);
1682 }
1683
1684 #[test]
1688 fn value_above_ceiling_rejected_without_allocation() {
1689 let path = temp_db_path("ceiling_reject");
1690 cleanup(&path);
1691 {
1692 let pager =
1693 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1694 let tree = BTree::new(pager.clone());
1695 let before = pager.page_count().expect("page_count() should succeed");
1696
1697 let value = vec![0u8; MAX_VALUE_SIZE + 1];
1698 let err = tree.insert(b"too_big", &value).unwrap_err();
1699 assert!(matches!(err, BTreeError::ValueTooLarge(_)));
1700
1701 assert_eq!(
1702 pager.page_count().expect("page_count() should succeed"),
1703 before,
1704 "rejected value must not allocate pages"
1705 );
1706 assert!(tree.is_empty());
1708 }
1709 cleanup(&path);
1710 }
1711
1712 #[test]
1717 fn bulk_insert_skips_oversized_row_keeps_rest() {
1718 let path = temp_db_path("bulk_mixed");
1719 cleanup(&path);
1720 {
1721 let pager =
1722 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1723 let tree = BTree::new(pager);
1724
1725 let mut items: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
1726 for i in 0..5u32 {
1728 items.push((format!("k{:04}", i).into_bytes(), vec![b'a' + i as u8; 64]));
1729 }
1730 items.push((b"k0005_huge".to_vec(), vec![0u8; MAX_VALUE_SIZE + 1]));
1732 for i in 6..11u32 {
1734 items.push((format!("k{:04}", i).into_bytes(), vec![b'z' - i as u8; 32]));
1735 }
1736
1737 let res = tree.bulk_insert_sorted(&items);
1738 assert!(matches!(res, Err(BTreeError::ValueTooLarge(_))));
1739
1740 for (k, v) in items.iter().filter(|(_, v)| v.len() <= MAX_VALUE_SIZE) {
1742 assert_eq!(
1743 tree.get(k).expect("get() should succeed").as_deref(),
1744 Some(v.as_slice()),
1745 "valid row {:?} must land",
1746 k
1747 );
1748 }
1749 assert_eq!(tree.get(b"k0005_huge").expect("get() should succeed"), None);
1751 }
1752 cleanup(&path);
1753 }
1754
1755 #[test]
1759 fn large_values_persist_transparently() {
1760 let path = temp_db_path("large_transparent");
1761 cleanup(&path);
1762 {
1763 let pager =
1764 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1765 let tree = BTree::new(pager);
1766
1767 for (i, size) in [
1772 OVERFLOW_THRESHOLD + 1,
1773 OVERFLOW_THRESHOLD * 2,
1774 64 * 1024,
1775 1 * 1024 * 1024,
1776 ]
1777 .iter()
1778 .enumerate()
1779 {
1780 let value: Vec<u8> = (0..*size).map(|n| ((n + i) & 0xff) as u8).collect();
1781 let key = format!("size{:08}", size).into_bytes();
1782 tree.insert(&key, &value).expect("insert() should succeed");
1783 assert_eq!(
1784 tree.get(&key).expect("get() should succeed").as_deref(),
1785 Some(value.as_slice()),
1786 "size {} must round-trip",
1787 size
1788 );
1789 }
1790 }
1791 cleanup(&path);
1792 }
1793}
1794
1795#[cfg(test)]
1798mod overflow_free_tests {
1799 use super::value_layout::OVERFLOW_THRESHOLD;
1800 use super::*;
1801 use std::path::PathBuf;
1802
1803 fn temp_db_path(tag: &str) -> PathBuf {
1804 use std::sync::atomic::{AtomicU64, Ordering};
1805 static COUNTER: AtomicU64 = AtomicU64::new(0);
1806 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
1807 let mut path = std::env::temp_dir();
1808 path.push(format!(
1809 "reddb_btree_free_{}_{}_{}.db",
1810 tag,
1811 std::process::id(),
1812 id
1813 ));
1814 path
1815 }
1816
1817 fn cleanup(path: &std::path::Path) {
1818 let _ = std::fs::remove_file(path);
1819 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
1820 let _ = std::fs::remove_file(sidecar);
1821 }
1822 }
1823
1824 fn incompressible(seed: u64, len: usize) -> Vec<u8> {
1825 let mut state = seed | 1;
1826 (0..len)
1827 .map(|_| {
1828 state ^= state << 13;
1829 state ^= state >> 7;
1830 state ^= state << 17;
1831 state as u8
1832 })
1833 .collect()
1834 }
1835
1836 #[test]
1842 fn delete_spilled_value_frees_overflow_chain() {
1843 let path = temp_db_path("delete_frees");
1844 cleanup(&path);
1845 {
1846 let pager =
1847 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1848 let tree = BTree::new(pager.clone());
1849
1850 let v1 = incompressible(0xA11CE, 2 * 1024 * 1024);
1852 tree.insert(b"k", &v1).expect("insert() should succeed");
1853 let after_first = pager.page_count().expect("page_count() should succeed");
1854
1855 assert!(tree.delete(b"k").expect("delete() should succeed"));
1857
1858 let v2 = incompressible(0xB0B, 2 * 1024 * 1024);
1861 tree.insert(b"k", &v2).expect("insert() should succeed");
1862 let after_second = pager.page_count().expect("page_count() should succeed");
1863
1864 assert_eq!(
1865 after_second, after_first,
1866 "second spill must reuse freed pages, not grow the file"
1867 );
1868 assert_eq!(
1869 tree.get(b"k").expect("get() should succeed").as_deref(),
1870 Some(v2.as_slice())
1871 );
1872 }
1873 cleanup(&path);
1874 }
1875
1876 #[test]
1881 fn shrinking_update_to_inline_frees_chain() {
1882 let path = temp_db_path("shrink_to_inline");
1883 cleanup(&path);
1884 {
1885 let pager =
1886 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1887 let tree = BTree::new(pager.clone());
1888
1889 let big = incompressible(0xDEAD, 2 * 1024 * 1024);
1890 tree.insert(b"k", &big).expect("insert() should succeed");
1891 let hwm = pager.page_count().expect("page_count() should succeed");
1892
1893 let small = b"tiny".to_vec();
1895 tree.upsert(b"k", &small).expect("upsert() should succeed");
1896 assert_eq!(
1897 tree.get(b"k").expect("get() should succeed").as_deref(),
1898 Some(small.as_slice())
1899 );
1900
1901 let big2 = incompressible(0xBEEF, 2 * 1024 * 1024);
1905 tree.insert(b"k2", &big2).expect("insert() should succeed");
1906 assert_eq!(
1907 pager.page_count().expect("page_count() should succeed"),
1908 hwm,
1909 "shrinking update must free the chain — replacement spill should reuse"
1910 );
1911 assert_eq!(
1912 tree.get(b"k2").expect("get() should succeed").as_deref(),
1913 Some(big2.as_slice())
1914 );
1915 }
1916 cleanup(&path);
1917 }
1918
1919 #[test]
1924 fn shrinking_update_to_shorter_spill_frees_old_chain() {
1925 let path = temp_db_path("shrink_spill_to_spill");
1926 cleanup(&path);
1927 {
1928 let pager =
1929 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1930 let tree = BTree::new(pager.clone());
1931
1932 let big = incompressible(0xF00D, 3 * 1024 * 1024);
1936 tree.insert(b"k", &big).expect("insert() should succeed");
1937 let hwm_after_big = pager.page_count().expect("page_count() should succeed");
1938
1939 let smaller = incompressible(0xCAFE, 1 * 1024 * 1024);
1950 tree.upsert(b"k", &smaller)
1951 .expect("upsert() should succeed");
1952 assert_eq!(
1953 tree.get(b"k").expect("get() should succeed").as_deref(),
1954 Some(smaller.as_slice())
1955 );
1956 let hwm_after_upsert = pager.page_count().expect("page_count() should succeed");
1957
1958 assert!(tree.delete(b"k").expect("delete() should succeed"));
1959
1960 let again = incompressible(0x1234, 3 * 1024 * 1024);
1961 tree.insert(b"k", &again).expect("insert() should succeed");
1962 assert!(
1963 pager.page_count().expect("page_count() should succeed") <= hwm_after_upsert,
1964 "after delete+insert of original size, file must not grow past the upsert transient \
1965 (page_count = {}, transient high-water = {}, original = {}) — the upsert must have \
1966 freed the old chain so the re-insert reuses freed pages",
1967 pager.page_count().expect("page_count() should succeed"),
1968 hwm_after_upsert,
1969 hwm_after_big,
1970 );
1971 assert_eq!(
1972 tree.get(b"k").expect("get() should succeed").as_deref(),
1973 Some(again.as_slice())
1974 );
1975 }
1976 cleanup(&path);
1977 }
1978
1979 #[test]
1982 fn insert_delete_reinsert_does_not_grow_file() {
1983 let path = temp_db_path("reinsert_no_grow");
1984 cleanup(&path);
1985 {
1986 let pager =
1987 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1988 let tree = BTree::new(pager.clone());
1989
1990 let v1 = incompressible(0x5EED, 4 * 1024 * 1024);
1991 tree.insert(b"row", &v1).expect("insert() should succeed");
1992 let pages_after_first = pager.page_count().expect("page_count() should succeed");
1993
1994 assert!(tree.delete(b"row").expect("delete() should succeed"));
1995
1996 let v2 = incompressible(0xACE, 4 * 1024 * 1024);
1997 tree.insert(b"row", &v2).expect("insert() should succeed");
1998 let pages_after_second = pager.page_count().expect("page_count() should succeed");
1999
2000 assert_eq!(
2001 pages_after_second, pages_after_first,
2002 "re-inserting at the same size must reuse the freed chain"
2003 );
2004 assert_eq!(
2005 tree.get(b"row").expect("get() should succeed").as_deref(),
2006 Some(v2.as_slice())
2007 );
2008 }
2009 cleanup(&path);
2010 }
2011
2012 #[test]
2017 fn upsert_batch_in_place_shrink_frees_chain() {
2018 let path = temp_db_path("batch_shrink");
2019 cleanup(&path);
2020 {
2021 let pager =
2022 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
2023 let tree = BTree::new(pager.clone());
2024
2025 let big_a = incompressible(0x1111, 2 * 1024 * 1024);
2028 let big_b = incompressible(0x2222, 2 * 1024 * 1024);
2029 tree.insert(b"a", &big_a).expect("insert() should succeed");
2030 tree.insert(b"b", &big_b).expect("insert() should succeed");
2031 let hwm = pager.page_count().expect("page_count() should succeed");
2032
2033 let updates = vec![
2034 (b"a".to_vec(), b"shrunk-a".to_vec()),
2035 (b"b".to_vec(), b"shrunk-b".to_vec()),
2036 ];
2037 tree.upsert_batch_sorted(&updates)
2038 .expect("upsert_batch_sorted() should succeed");
2039 assert_eq!(
2040 tree.get(b"a").expect("get() should succeed").as_deref(),
2041 Some(b"shrunk-a".as_slice())
2042 );
2043 assert_eq!(
2044 tree.get(b"b").expect("get() should succeed").as_deref(),
2045 Some(b"shrunk-b".as_slice())
2046 );
2047
2048 let again_a = incompressible(0xAAAA, 2 * 1024 * 1024);
2051 let again_b = incompressible(0xBBBB, 2 * 1024 * 1024);
2052 tree.insert(b"c", &again_a)
2053 .expect("insert() should succeed");
2054 tree.insert(b"d", &again_b)
2055 .expect("insert() should succeed");
2056 assert!(
2057 pager.page_count().expect("page_count() should succeed") <= hwm,
2058 "batch in-place shrink must free chains — replacement spills should reuse"
2059 );
2060 }
2061 cleanup(&path);
2062 }
2063}