1use super::pager::Pager;
3pub use reddb_engine::storage::btree::{
4 lehman_yao, BTreeError, BTreeResult, MAX_KEY_SIZE, MAX_VALUE_SIZE, MIN_FILL_FACTOR,
5};
6#[cfg(test)]
7use std::sync::Arc;
8pub type BTree = reddb_engine::storage::btree::BTree<Pager>;
9pub type BTreeCursor = reddb_engine::storage::btree::BTreeCursor<Pager>;
10#[path = "btree/value_layout.rs"]
11pub mod value_layout;
12
13#[cfg(test)]
14mod tests {
15 use super::*;
16 use std::path::PathBuf;
17
18 struct TempDbPath(PathBuf);
25 impl std::ops::Deref for TempDbPath {
26 type Target = PathBuf;
27 fn deref(&self) -> &PathBuf {
28 &self.0
29 }
30 }
31 impl AsRef<std::path::Path> for TempDbPath {
32 fn as_ref(&self) -> &std::path::Path {
33 self.0.as_ref()
34 }
35 }
36 impl Drop for TempDbPath {
37 fn drop(&mut self) {
38 cleanup(&self.0);
39 }
40 }
41
42 fn temp_db_path() -> TempDbPath {
43 use std::sync::atomic::{AtomicU64, Ordering};
44 static COUNTER: AtomicU64 = AtomicU64::new(0);
45 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
46 let mut path = std::env::temp_dir();
47 path.push(format!("reddb_btree_test_{}_{}.db", std::process::id(), id));
48 TempDbPath(path)
49 }
50
51 fn cleanup(path: &PathBuf) {
52 if let (Some(dir), Some(name)) = (
56 path.parent(),
57 path.file_name().and_then(|name| name.to_str()),
58 ) {
59 if let Ok(entries) = std::fs::read_dir(dir) {
60 for entry in entries.flatten() {
61 if entry
62 .file_name()
63 .to_str()
64 .is_some_and(|entry_name| entry_name.starts_with(name))
65 {
66 let _ = std::fs::remove_file(entry.path());
67 }
68 }
69 }
70 }
71 }
72
73 #[test]
74 fn test_btree_empty() {
75 let path = temp_db_path();
76 cleanup(&path);
77
78 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
79 let tree = BTree::new(pager);
80
81 assert!(tree.is_empty());
82 assert_eq!(tree.root_page_id(), 0);
83 assert_eq!(tree.get(b"key").expect("get() should succeed"), None);
84 assert_eq!(tree.count().expect("count() should succeed"), 0);
85
86 cleanup(&path);
87 }
88
89 #[test]
90 fn inline_batch_fallback_does_not_partially_update_tree() {
91 let path = temp_db_path();
92 let pager = Arc::new(Pager::open_default(&path).expect("pager"));
93 let tree = BTree::new(pager);
94 tree.insert(b"a", b"old").expect("insert");
95 let root = tree.root_page_id();
96 let mut random = 0x1234_5678_u32;
97 let large: Vec<u8> = (0..reddb_engine::storage::btree::value_layout::OVERFLOW_THRESHOLD
98 * 3)
99 .map(|_| {
100 random ^= random << 13;
101 random ^= random >> 17;
102 random ^= random << 5;
103 random as u8
104 })
105 .collect();
106 let unsupported = vec![
107 (b"a".to_vec(), b"new".to_vec()),
108 (b"missing".to_vec(), large),
109 ];
110 assert!(!tree
111 .try_upsert_inline_without_splits(&unsupported)
112 .expect("preflight"));
113 assert_eq!(tree.get(b"a").expect("read"), Some(b"old".to_vec()));
114 assert_eq!(tree.get(b"missing").expect("read"), None);
115 assert!(tree
116 .try_upsert_inline_without_splits(&[(b"missing".to_vec(), b"x".to_vec())])
117 .expect("insert"));
118 assert_eq!(tree.get(b"missing").expect("read"), Some(b"x".to_vec()));
119 assert!(tree
120 .try_upsert_inline_without_splits(&[(b"a".to_vec(), b"longer".to_vec())])
121 .expect("repack"));
122 assert_eq!(tree.get(b"a").expect("read"), Some(b"longer".to_vec()));
123 assert!(tree
124 .try_upsert_inline_without_splits(&[(b"a".to_vec(), b"new".to_vec())])
125 .expect("overwrite"));
126 assert_eq!(tree.get(b"a").expect("read"), Some(b"new".to_vec()));
127 assert_eq!(tree.root_page_id(), root);
128 }
129
130 #[test]
131 fn test_btree_single_insert() {
132 let path = temp_db_path();
133 cleanup(&path);
134
135 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
136 let tree = BTree::new(pager);
137
138 tree.insert(b"hello", b"world")
139 .expect("insert() should succeed");
140
141 assert!(!tree.is_empty());
142 assert_eq!(
143 tree.get(b"hello").expect("get() should succeed"),
144 Some(b"world".to_vec())
145 );
146 assert_eq!(tree.get(b"other").expect("get() should succeed"), None);
147 assert_eq!(tree.count().expect("count() should succeed"), 1);
148
149 cleanup(&path);
150 }
151
152 #[test]
153 fn test_btree_multiple_inserts() {
154 let path = temp_db_path();
155 cleanup(&path);
156
157 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
158 let tree = BTree::new(pager);
159
160 tree.insert(b"c", b"3").expect("insert() should succeed");
161 tree.insert(b"a", b"1").expect("insert() should succeed");
162 tree.insert(b"b", b"2").expect("insert() should succeed");
163
164 assert_eq!(
165 tree.get(b"a").expect("get() should succeed"),
166 Some(b"1".to_vec())
167 );
168 assert_eq!(
169 tree.get(b"b").expect("get() should succeed"),
170 Some(b"2".to_vec())
171 );
172 assert_eq!(
173 tree.get(b"c").expect("get() should succeed"),
174 Some(b"3".to_vec())
175 );
176 assert_eq!(tree.count().expect("count() should succeed"), 3);
177
178 cleanup(&path);
179 }
180
181 #[test]
182 fn test_btree_duplicate_key() {
183 let path = temp_db_path();
184 cleanup(&path);
185
186 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
187 let tree = BTree::new(pager);
188
189 tree.insert(b"key", b"value1")
190 .expect("insert() should succeed");
191 let result = tree.insert(b"key", b"value2");
192
193 assert!(matches!(result, Err(BTreeError::DuplicateKey)));
194 assert_eq!(
195 tree.get(b"key").expect("get() should succeed"),
196 Some(b"value1".to_vec())
197 );
198
199 cleanup(&path);
200 }
201
202 #[test]
203 fn test_btree_delete() {
204 let path = temp_db_path();
205 cleanup(&path);
206
207 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
208 let tree = BTree::new(pager);
209
210 tree.insert(b"a", b"1").expect("insert() should succeed");
211 tree.insert(b"b", b"2").expect("insert() should succeed");
212 tree.insert(b"c", b"3").expect("insert() should succeed");
213
214 assert!(tree.delete(b"b").expect("delete() should succeed"));
215 assert!(!tree.delete(b"d").expect("delete() should succeed"));
216
217 assert_eq!(
218 tree.get(b"a").expect("get() should succeed"),
219 Some(b"1".to_vec())
220 );
221 assert_eq!(tree.get(b"b").expect("get() should succeed"), None);
222 assert_eq!(
223 tree.get(b"c").expect("get() should succeed"),
224 Some(b"3".to_vec())
225 );
226 assert_eq!(tree.count().expect("count() should succeed"), 2);
227
228 cleanup(&path);
229 }
230
231 #[test]
232 fn test_btree_delete_rebalance_removes_empty_leaf() {
233 let path = temp_db_path();
234 cleanup(&path);
235
236 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
237 let tree = BTree::new(pager.clone());
238
239 let value = vec![b'v'; 200];
240 for i in 0..300u32 {
241 let key = format!("key{:03}", i);
242 tree.insert(key.as_bytes(), &value)
243 .expect("insert() should succeed");
244 }
245
246 let keys: Vec<_> = (10..290u32)
248 .map(|id| format!("key{id:03}").into_bytes())
249 .collect();
250
251 for key in &keys {
252 tree.delete(key).expect("delete() should succeed");
253 }
254
255 let expected = 300 - keys.len();
256 assert_eq!(tree.count().expect("count() should succeed"), expected);
257
258 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
259 let mut results = Vec::new();
260 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
261 results.push(key);
262 }
263
264 assert_eq!(results.len(), expected);
265 let last_key = format!("key{:03}", 299).into_bytes();
266 assert_eq!(results.last(), Some(&last_key));
267
268 cleanup(&path);
269 }
270
271 #[test]
272 fn test_btree_cursor() {
273 let path = temp_db_path();
274 cleanup(&path);
275
276 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
277 let tree = BTree::new(pager);
278
279 tree.insert(b"c", b"3").expect("insert() should succeed");
280 tree.insert(b"a", b"1").expect("insert() should succeed");
281 tree.insert(b"b", b"2").expect("insert() should succeed");
282
283 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
284 let mut results: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
285
286 while let Some(entry) = cursor.next().expect("next() should succeed") {
287 results.push(entry);
288 }
289
290 assert_eq!(results.len(), 3);
291 assert_eq!(results[0], (b"a".to_vec(), b"1".to_vec()));
292 assert_eq!(results[1], (b"b".to_vec(), b"2".to_vec()));
293 assert_eq!(results[2], (b"c".to_vec(), b"3".to_vec()));
294
295 cleanup(&path);
296 }
297
298 #[test]
299 fn test_btree_range() {
300 let path = temp_db_path();
301 cleanup(&path);
302
303 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
304 let tree = BTree::new(pager);
305
306 for i in 0..10u8 {
307 let key = format!("key{:02}", i);
308 let value = format!("val{:02}", i);
309 tree.insert(key.as_bytes(), value.as_bytes())
310 .expect("insert() should succeed");
311 }
312
313 let results = tree
314 .range(b"key03", b"key06")
315 .expect("range() should succeed");
316
317 assert_eq!(results.len(), 4);
318 assert_eq!(results[0].0, b"key03".to_vec());
319 assert_eq!(results[3].0, b"key06".to_vec());
320
321 cleanup(&path);
322 }
323
324 #[test]
325 fn test_btree_large_keys() {
326 let path = temp_db_path();
327 cleanup(&path);
328
329 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
330 let tree = BTree::new(pager);
331
332 let key = vec![b'x'; 500];
333 let value = vec![b'y'; 500];
334
335 tree.insert(&key, &value).expect("insert() should succeed");
336 assert_eq!(tree.get(&key).expect("get() should succeed"), Some(value));
337
338 cleanup(&path);
339 }
340
341 #[test]
342 fn test_btree_key_too_large() {
343 let path = temp_db_path();
344 cleanup(&path);
345
346 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
347 let tree = BTree::new(pager);
348
349 let key = vec![b'x'; MAX_KEY_SIZE + 1];
350 let result = tree.insert(&key, b"value");
351
352 assert!(matches!(result, Err(BTreeError::KeyTooLarge(_))));
353
354 cleanup(&path);
355 }
356
357 #[test]
358 fn test_btree_many_inserts() {
359 let path = temp_db_path();
360 cleanup(&path);
361
362 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
363 let tree = BTree::new(pager);
364
365 for i in 0..100u32 {
367 let key = format!("key{:08}", i);
368 let value = format!("value{:08}", i);
369 tree.insert(key.as_bytes(), value.as_bytes())
370 .expect("insert() should succeed");
371 }
372
373 for i in 0..100u32 {
375 let key = format!("key{:08}", i);
376 let expected = format!("value{:08}", i);
377 assert_eq!(
378 tree.get(key.as_bytes()).expect("get() should succeed"),
379 Some(expected.into_bytes())
380 );
381 }
382
383 assert_eq!(tree.count().expect("count() should succeed"), 100);
384
385 cleanup(&path);
386 }
387
388 #[test]
389 fn test_btree_bulk_insert_sorted_preserves_leaf_layout() {
390 let path = temp_db_path();
391 cleanup(&path);
392
393 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
394 let tree = BTree::new(pager);
395
396 let items: Vec<(Vec<u8>, Vec<u8>)> = (0..240u32)
397 .map(|i| {
398 (
399 format!("key{:08}", i).into_bytes(),
400 vec![b'v'; 32 + (i as usize % 7)],
401 )
402 })
403 .collect();
404
405 tree.bulk_insert_sorted(&items)
406 .expect("bulk_insert_sorted() should succeed");
407
408 assert_eq!(tree.count().expect("count() should succeed"), items.len());
409
410 for (key, value) in &items {
411 assert_eq!(
412 tree.get(key).expect("get() should succeed"),
413 Some(value.clone())
414 );
415 }
416
417 cleanup(&path);
418 }
419
420 mod prop_upsert_batch_sorted {
430 use super::*;
431 use proptest::prelude::*;
432 use std::collections::BTreeMap;
433
434 fn populate_tree(seed: &[(Vec<u8>, Vec<u8>)]) -> (BTree, TempDbPath, Arc<Pager>) {
437 let path = temp_db_path();
438 cleanup(&path);
439 let pager =
440 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
441 let tree = BTree::new(Arc::clone(&pager));
442 for (k, v) in seed {
443 tree.upsert(k, v).expect("upsert() should succeed");
444 }
445 (tree, path, pager)
446 }
447
448 fn key_strategy() -> impl Strategy<Value = Vec<u8>> {
449 prop::collection::vec(0u8..16u8, 1..=4)
453 }
454
455 fn value_strategy() -> impl Strategy<Value = Vec<u8>> {
456 prop::collection::vec(0u8..255u8, 0..=8)
460 }
461
462 fn pair_strategy() -> impl Strategy<Value = (Vec<u8>, Vec<u8>)> {
463 (key_strategy(), value_strategy())
464 }
465
466 proptest! {
467 #![proptest_config(ProptestConfig {
468 cases: 64,
469 .. ProptestConfig::default()
470 })]
471
472 #[test]
473 fn batch_upsert_matches_loop_upsert(
474 seed in prop::collection::vec(pair_strategy(), 0..50),
475 batch in prop::collection::vec(pair_strategy(), 1..200),
476 ) {
477 let mut seed_map: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
480 for (k, v) in seed {
481 seed_map.insert(k, v);
482 }
483 let seed: Vec<(Vec<u8>, Vec<u8>)> = seed_map.into_iter().collect();
484
485 let (baseline, baseline_path, _baseline_pager) = populate_tree(&seed);
487 for (k, v) in &batch {
488 baseline.upsert(k, v).expect("upsert() should succeed");
489 }
490
491 let (candidate, candidate_path, _candidate_pager) = populate_tree(&seed);
494 let mut sorted = batch.clone();
495 sorted.sort_by(|a, b| a.0.cmp(&b.0));
496 candidate.upsert_batch_sorted(&sorted).expect("upsert_batch_sorted() should succeed");
497
498 let mut expected: BTreeMap<Vec<u8>, Vec<u8>> = BTreeMap::new();
501 for (k, v) in &seed {
502 expected.insert(k.clone(), v.clone());
503 }
504 for (k, v) in &batch {
505 expected.insert(k.clone(), v.clone());
506 }
507
508 for (k, v) in &expected {
510 let baseline_got = baseline.get(k).expect("get() should succeed");
511 let candidate_got = candidate.get(k).expect("get() should succeed");
512 prop_assert_eq!(baseline_got.as_ref(), Some(v));
513 prop_assert_eq!(candidate_got.as_ref(), Some(v));
514 }
515
516 let collect = |t: &BTree| -> Vec<(Vec<u8>, Vec<u8>)> {
518 let mut out = Vec::new();
519 let mut cur = t.cursor_first().expect("cursor_first() should succeed");
520 while let Some(entry) = cur.next().expect("next() should succeed") {
521 out.push(entry);
522 }
523 out
524 };
525 let baseline_contents = collect(&baseline);
526 let candidate_contents = collect(&candidate);
527 prop_assert_eq!(&baseline_contents, &candidate_contents);
528
529 let expected_contents: Vec<(Vec<u8>, Vec<u8>)> =
531 expected.into_iter().collect();
532 prop_assert_eq!(&candidate_contents, &expected_contents);
533
534 drop(baseline);
542 drop(candidate);
543 drop(_baseline_pager);
544 drop(_candidate_pager);
545 cleanup(&baseline_path);
546 cleanup(&candidate_path);
547 }
548 }
549 }
550
551 #[test]
552 fn test_btree_sorted_iteration() {
553 let path = temp_db_path();
554 cleanup(&path);
555
556 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
557 let tree = BTree::new(pager);
558
559 let keys = vec![50, 25, 75, 10, 30, 60, 80, 5, 15, 27, 35, 55, 65, 77, 90];
561 for k in &keys {
562 let key = format!("{:03}", k);
563 tree.insert(key.as_bytes(), key.as_bytes())
564 .expect("insert() should succeed");
565 }
566
567 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
569 let mut prev: Option<Vec<u8>> = None;
570
571 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
572 if let Some(p) = &prev {
573 assert!(p < &key, "Keys not in sorted order");
574 }
575 prev = Some(key);
576 }
577
578 cleanup(&path);
579 }
580
581 #[test]
586 fn concurrent_writers_on_one_tree_keep_every_key() {
587 const WRITER_COUNT: u32 = 4;
588 const KEY_COUNT_PER_WRITER: u32 = 2_000;
589
590 let path = temp_db_path();
591 cleanup(&path);
592 let pager = Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
593 let tree = Arc::new(BTree::new(pager));
594 let value = vec![7u8; 96];
595
596 let writers: Vec<_> = (0..WRITER_COUNT)
597 .map(|writer| {
598 let tree = Arc::clone(&tree);
599 let value = value.clone();
600 std::thread::spawn(move || {
601 for i in 0..KEY_COUNT_PER_WRITER {
602 let key = (i * WRITER_COUNT + writer).to_be_bytes();
604 tree.insert(&key, &value).expect("insert() should succeed");
605 if i % 4 == 0 {
607 assert!(tree.delete(&key).expect("delete() should succeed"));
608 tree.upsert(&key, &value).expect("upsert() should succeed");
609 }
610 }
611 })
612 })
613 .collect();
614 for writer in writers {
615 writer.join().expect("writer thread panicked");
616 }
617
618 let key_count = WRITER_COUNT * KEY_COUNT_PER_WRITER;
619 let mut cursor = tree.cursor_first().expect("cursor_first() should succeed");
620 let mut seen = Vec::new();
621 while let Some((key, _)) = cursor.next().expect("next() should succeed") {
622 seen.push(u32::from_be_bytes(
623 key.as_slice().try_into().expect("invariant: 4-byte keys"),
624 ));
625 }
626 let expected: Vec<u32> = (0..key_count).collect();
627 assert_eq!(seen, expected, "every key exactly once, in order");
628 for key in 0..key_count {
629 assert!(
630 tree.get(&key.to_be_bytes())
631 .expect("get() should succeed")
632 .is_some(),
633 "key {key} must be reachable from the root"
634 );
635 }
636
637 cleanup(&path);
638 }
639}
640
641#[cfg(test)]
646mod overflow_pipeline_tests {
647 use super::value_layout::{MAX_VALUE_SIZE, OVERFLOW_THRESHOLD};
648 use super::*;
649 use std::path::PathBuf;
650
651 fn temp_db_path(tag: &str) -> PathBuf {
652 use std::sync::atomic::{AtomicU64, Ordering};
653 static COUNTER: AtomicU64 = AtomicU64::new(0);
654 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
655 let mut path = std::env::temp_dir();
656 path.push(format!(
657 "reddb_btree_spill_{}_{}_{}.db",
658 tag,
659 std::process::id(),
660 id
661 ));
662 path
663 }
664
665 fn cleanup(path: &std::path::Path) {
666 let _ = std::fs::remove_file(path);
667 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
668 let _ = std::fs::remove_file(sidecar);
669 }
670 }
671
672 #[test]
677 fn inline_path_allocates_no_overflow_pages() {
678 let path = temp_db_path("inline_no_overflow");
679 cleanup(&path);
680 {
681 let pager =
682 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
683 let tree = BTree::new(pager.clone());
684 let before = pager.page_count().expect("page_count() should succeed");
685
686 let value = vec![0x5Au8; OVERFLOW_THRESHOLD - 1];
687 tree.insert(b"k", &value).expect("insert() should succeed");
688
689 let after = pager.page_count().expect("page_count() should succeed");
690 assert_eq!(
693 after - before,
694 1,
695 "inline value must allocate only the root leaf"
696 );
697 assert_eq!(tree.get(b"k").expect("get() should succeed"), Some(value));
698 }
699 cleanup(&path);
700 }
701
702 #[test]
706 fn compressible_44kb_round_trips() {
707 let path = temp_db_path("compressible_44kb");
708 cleanup(&path);
709 {
710 let pager =
711 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
712 let tree = BTree::new(pager);
713
714 let snippet = "# heading\n- bullet line that says the same thing over and over\n";
716 let mut value = Vec::with_capacity(44 * 1024);
717 while value.len() < 44 * 1024 {
718 value.extend_from_slice(snippet.as_bytes());
719 }
720 assert!(value.len() > OVERFLOW_THRESHOLD);
721
722 tree.insert(b"doc", &value)
723 .expect("insert() should succeed");
724 assert_eq!(tree.get(b"doc").expect("get() should succeed"), Some(value));
725 }
726 cleanup(&path);
727 }
728
729 #[test]
732 fn incompressible_5mb_spills_and_round_trips() {
733 let path = temp_db_path("incompressible_5mb");
734 cleanup(&path);
735 {
736 let pager =
737 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
738 let tree = BTree::new(pager.clone());
739
740 let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
743 let value: Vec<u8> = (0..5 * 1024 * 1024)
744 .map(|_| {
745 state ^= state << 13;
746 state ^= state >> 7;
747 state ^= state << 17;
748 state as u8
749 })
750 .collect();
751
752 let before = pager.page_count().expect("page_count() should succeed");
753 tree.insert(b"blob", &value)
754 .expect("insert() should succeed");
755 let after = pager.page_count().expect("page_count() should succeed");
756 assert!(
759 after - before > 2,
760 "5 MB spill must allocate overflow pages (alloc'd {} pages)",
761 after - before
762 );
763
764 assert_eq!(
765 tree.get(b"blob").expect("get() should succeed").as_deref(),
766 Some(value.as_slice())
767 );
768 }
769 cleanup(&path);
770 }
771
772 #[test]
776 fn value_above_ceiling_rejected_without_allocation() {
777 let path = temp_db_path("ceiling_reject");
778 cleanup(&path);
779 {
780 let pager =
781 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
782 let tree = BTree::new(pager.clone());
783 let before = pager.page_count().expect("page_count() should succeed");
784
785 let value = vec![0u8; MAX_VALUE_SIZE + 1];
786 let err = tree.insert(b"too_big", &value).unwrap_err();
787 assert!(matches!(err, BTreeError::ValueTooLarge(_)));
788
789 assert_eq!(
790 pager.page_count().expect("page_count() should succeed"),
791 before,
792 "rejected value must not allocate pages"
793 );
794 assert!(tree.is_empty());
796 }
797 cleanup(&path);
798 }
799
800 #[test]
805 fn bulk_insert_skips_oversized_row_keeps_rest() {
806 let path = temp_db_path("bulk_mixed");
807 cleanup(&path);
808 {
809 let pager =
810 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
811 let tree = BTree::new(pager);
812
813 let mut items: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
814 for i in 0..5u32 {
816 items.push((format!("k{:04}", i).into_bytes(), vec![b'a' + i as u8; 64]));
817 }
818 items.push((b"k0005_huge".to_vec(), vec![0u8; MAX_VALUE_SIZE + 1]));
820 for i in 6..11u32 {
822 items.push((format!("k{:04}", i).into_bytes(), vec![b'z' - i as u8; 32]));
823 }
824
825 let res = tree.bulk_insert_sorted(&items);
826 assert!(matches!(res, Err(BTreeError::ValueTooLarge(_))));
827
828 for (k, v) in items.iter().filter(|(_, v)| v.len() <= MAX_VALUE_SIZE) {
830 assert_eq!(
831 tree.get(k).expect("get() should succeed").as_deref(),
832 Some(v.as_slice()),
833 "valid row {:?} must land",
834 k
835 );
836 }
837 assert_eq!(tree.get(b"k0005_huge").expect("get() should succeed"), None);
839 }
840 cleanup(&path);
841 }
842
843 #[test]
847 fn large_values_persist_transparently() {
848 let path = temp_db_path("large_transparent");
849 cleanup(&path);
850 {
851 let pager =
852 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
853 let tree = BTree::new(pager);
854
855 for (i, size) in [
860 OVERFLOW_THRESHOLD + 1,
861 OVERFLOW_THRESHOLD * 2,
862 64 * 1024,
863 1 * 1024 * 1024,
864 ]
865 .iter()
866 .enumerate()
867 {
868 let value: Vec<u8> = (0..*size).map(|n| ((n + i) & 0xff) as u8).collect();
869 let key = format!("size{:08}", size).into_bytes();
870 tree.insert(&key, &value).expect("insert() should succeed");
871 assert_eq!(
872 tree.get(&key).expect("get() should succeed").as_deref(),
873 Some(value.as_slice()),
874 "size {} must round-trip",
875 size
876 );
877 }
878 }
879 cleanup(&path);
880 }
881}
882
883#[cfg(test)]
886mod overflow_free_tests {
887 use super::value_layout::OVERFLOW_THRESHOLD;
888 use super::*;
889 use std::path::PathBuf;
890
891 fn temp_db_path(tag: &str) -> PathBuf {
892 use std::sync::atomic::{AtomicU64, Ordering};
893 static COUNTER: AtomicU64 = AtomicU64::new(0);
894 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
895 let mut path = std::env::temp_dir();
896 path.push(format!(
897 "reddb_btree_free_{}_{}_{}.db",
898 tag,
899 std::process::id(),
900 id
901 ));
902 path
903 }
904
905 fn cleanup(path: &std::path::Path) {
906 let _ = std::fs::remove_file(path);
907 for sidecar in reddb_file::layout::pager_shadow_sidecar_paths(path) {
908 let _ = std::fs::remove_file(sidecar);
909 }
910 }
911
912 fn incompressible(seed: u64, len: usize) -> Vec<u8> {
913 let mut state = seed | 1;
914 (0..len)
915 .map(|_| {
916 state ^= state << 13;
917 state ^= state >> 7;
918 state ^= state << 17;
919 state as u8
920 })
921 .collect()
922 }
923
924 #[test]
930 fn delete_spilled_value_frees_overflow_chain() {
931 let path = temp_db_path("delete_frees");
932 cleanup(&path);
933 {
934 let pager =
935 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
936 let tree = BTree::new(pager.clone());
937
938 let v1 = incompressible(0xA11CE, 2 * 1024 * 1024);
940 tree.insert(b"k", &v1).expect("insert() should succeed");
941 let after_first = pager.page_count().expect("page_count() should succeed");
942
943 assert!(tree.delete(b"k").expect("delete() should succeed"));
945
946 let v2 = incompressible(0xB0B, 2 * 1024 * 1024);
949 tree.insert(b"k", &v2).expect("insert() should succeed");
950 let after_second = pager.page_count().expect("page_count() should succeed");
951
952 assert_eq!(
953 after_second, after_first,
954 "second spill must reuse freed pages, not grow the file"
955 );
956 assert_eq!(
957 tree.get(b"k").expect("get() should succeed").as_deref(),
958 Some(v2.as_slice())
959 );
960 }
961 cleanup(&path);
962 }
963
964 #[test]
969 fn shrinking_update_to_inline_frees_chain() {
970 let path = temp_db_path("shrink_to_inline");
971 cleanup(&path);
972 {
973 let pager =
974 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
975 let tree = BTree::new(pager.clone());
976
977 let big = incompressible(0xDEAD, 2 * 1024 * 1024);
978 tree.insert(b"k", &big).expect("insert() should succeed");
979 let hwm = pager.page_count().expect("page_count() should succeed");
980
981 let small = b"tiny".to_vec();
983 tree.upsert(b"k", &small).expect("upsert() should succeed");
984 assert_eq!(
985 tree.get(b"k").expect("get() should succeed").as_deref(),
986 Some(small.as_slice())
987 );
988
989 let big2 = incompressible(0xBEEF, 2 * 1024 * 1024);
993 tree.insert(b"k2", &big2).expect("insert() should succeed");
994 assert_eq!(
995 pager.page_count().expect("page_count() should succeed"),
996 hwm,
997 "shrinking update must free the chain — replacement spill should reuse"
998 );
999 assert_eq!(
1000 tree.get(b"k2").expect("get() should succeed").as_deref(),
1001 Some(big2.as_slice())
1002 );
1003 }
1004 cleanup(&path);
1005 }
1006
1007 #[test]
1012 fn shrinking_update_to_shorter_spill_frees_old_chain() {
1013 let path = temp_db_path("shrink_spill_to_spill");
1014 cleanup(&path);
1015 {
1016 let pager =
1017 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1018 let tree = BTree::new(pager.clone());
1019
1020 let big = incompressible(0xF00D, 3 * 1024 * 1024);
1024 tree.insert(b"k", &big).expect("insert() should succeed");
1025 let hwm_after_big = pager.page_count().expect("page_count() should succeed");
1026
1027 let smaller = incompressible(0xCAFE, 1 * 1024 * 1024);
1038 tree.upsert(b"k", &smaller)
1039 .expect("upsert() should succeed");
1040 assert_eq!(
1041 tree.get(b"k").expect("get() should succeed").as_deref(),
1042 Some(smaller.as_slice())
1043 );
1044 let hwm_after_upsert = pager.page_count().expect("page_count() should succeed");
1045
1046 assert!(tree.delete(b"k").expect("delete() should succeed"));
1047
1048 let again = incompressible(0x1234, 3 * 1024 * 1024);
1049 tree.insert(b"k", &again).expect("insert() should succeed");
1050 assert!(
1051 pager.page_count().expect("page_count() should succeed") <= hwm_after_upsert,
1052 "after delete+insert of original size, file must not grow past the upsert transient \
1053 (page_count = {}, transient high-water = {}, original = {}) — the upsert must have \
1054 freed the old chain so the re-insert reuses freed pages",
1055 pager.page_count().expect("page_count() should succeed"),
1056 hwm_after_upsert,
1057 hwm_after_big,
1058 );
1059 assert_eq!(
1060 tree.get(b"k").expect("get() should succeed").as_deref(),
1061 Some(again.as_slice())
1062 );
1063 }
1064 cleanup(&path);
1065 }
1066
1067 #[test]
1070 fn insert_delete_reinsert_does_not_grow_file() {
1071 let path = temp_db_path("reinsert_no_grow");
1072 cleanup(&path);
1073 {
1074 let pager =
1075 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1076 let tree = BTree::new(pager.clone());
1077
1078 let v1 = incompressible(0x5EED, 4 * 1024 * 1024);
1079 tree.insert(b"row", &v1).expect("insert() should succeed");
1080 let pages_after_first = pager.page_count().expect("page_count() should succeed");
1081
1082 assert!(tree.delete(b"row").expect("delete() should succeed"));
1083
1084 let v2 = incompressible(0xACE, 4 * 1024 * 1024);
1085 tree.insert(b"row", &v2).expect("insert() should succeed");
1086 let pages_after_second = pager.page_count().expect("page_count() should succeed");
1087
1088 assert_eq!(
1089 pages_after_second, pages_after_first,
1090 "re-inserting at the same size must reuse the freed chain"
1091 );
1092 assert_eq!(
1093 tree.get(b"row").expect("get() should succeed").as_deref(),
1094 Some(v2.as_slice())
1095 );
1096 }
1097 cleanup(&path);
1098 }
1099
1100 #[test]
1105 fn upsert_batch_in_place_shrink_frees_chain() {
1106 let path = temp_db_path("batch_shrink");
1107 cleanup(&path);
1108 {
1109 let pager =
1110 Arc::new(Pager::open_default(&path).expect("open_default() should succeed"));
1111 let tree = BTree::new(pager.clone());
1112
1113 let big_a = incompressible(0x1111, 2 * 1024 * 1024);
1116 let big_b = incompressible(0x2222, 2 * 1024 * 1024);
1117 tree.insert(b"a", &big_a).expect("insert() should succeed");
1118 tree.insert(b"b", &big_b).expect("insert() should succeed");
1119 let hwm = pager.page_count().expect("page_count() should succeed");
1120
1121 let updates = vec![
1122 (b"a".to_vec(), b"shrunk-a".to_vec()),
1123 (b"b".to_vec(), b"shrunk-b".to_vec()),
1124 ];
1125 tree.upsert_batch_sorted(&updates)
1126 .expect("upsert_batch_sorted() should succeed");
1127 assert_eq!(
1128 tree.get(b"a").expect("get() should succeed").as_deref(),
1129 Some(b"shrunk-a".as_slice())
1130 );
1131 assert_eq!(
1132 tree.get(b"b").expect("get() should succeed").as_deref(),
1133 Some(b"shrunk-b".as_slice())
1134 );
1135
1136 let again_a = incompressible(0xAAAA, 2 * 1024 * 1024);
1139 let again_b = incompressible(0xBBBB, 2 * 1024 * 1024);
1140 tree.insert(b"c", &again_a)
1141 .expect("insert() should succeed");
1142 tree.insert(b"d", &again_b)
1143 .expect("insert() should succeed");
1144 assert!(
1145 pager.page_count().expect("page_count() should succeed") <= hwm,
1146 "batch in-place shrink must free chains — replacement spills should reuse"
1147 );
1148 }
1149 cleanup(&path);
1150 }
1151}