tests + file_storage
This commit is contained in:
parent
bf4200e9e5
commit
8f3f358284
8 changed files with 490 additions and 10 deletions
166
Cargo.lock
generated
166
Cargo.lock
generated
|
|
@ -2,6 +2,47 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||||
|
dependencies = [
|
||||||
|
"bincode_derive",
|
||||||
|
"serde",
|
||||||
|
"unty",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode_derive"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
|
||||||
|
dependencies = [
|
||||||
|
"virtue",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
|
|
@ -12,6 +53,8 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
name = "btree"
|
name = "btree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"proptest",
|
||||||
"rand",
|
"rand",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
@ -38,6 +81,12 @@ version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
@ -62,6 +111,15 @@ version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
|
|
@ -86,6 +144,31 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proptest"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"bit-vec",
|
||||||
|
"bitflags",
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_xorshift",
|
||||||
|
"regex-syntax",
|
||||||
|
"rusty-fork",
|
||||||
|
"tempfile",
|
||||||
|
"unarray",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.42"
|
version = "1.0.42"
|
||||||
|
|
@ -130,6 +213,21 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xorshift"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
|
|
@ -143,6 +241,47 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty-fork"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"quick-error",
|
||||||
|
"tempfile",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.111"
|
version = "2.0.111"
|
||||||
|
|
@ -167,12 +306,39 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unarray"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unty"
|
||||||
|
version = "0.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtue"
|
||||||
|
version = "0.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.1+wasi-0.2.4"
|
version = "1.0.1+wasi-0.2.4"
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bincode = "2.0.1"
|
||||||
|
proptest = "1.9.0"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
tempfile = "3.23.0"
|
tempfile = "3.23.0"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find(&self, key: i32) -> Option<Record> {
|
pub fn find(&mut self, key: i32) -> Option<Record> {
|
||||||
let mut current_loc = self.root_loc;
|
let mut current_loc = self.root_loc;
|
||||||
loop {
|
loop {
|
||||||
let node = self.storage.read_node(current_loc)?;
|
let node = self.storage.read_node(current_loc)?;
|
||||||
|
|
@ -286,7 +286,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_non_existent_key() {
|
fn test_non_existent_key() {
|
||||||
let storage = InMemoryStorage::new();
|
let storage = InMemoryStorage::new();
|
||||||
let tree = BPlusTree::open(storage);
|
let mut tree = BPlusTree::open(storage);
|
||||||
assert_eq!(tree.find(999), None); // Assuming 999 is not in the tree
|
assert_eq!(tree.find(999), None); // Assuming 999 is not in the tree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
pub const DEGREE: usize = 2;
|
pub const DEGREE: usize = 2;
|
||||||
pub const MAX_KEYS: usize = 4;
|
pub const MAX_KEYS: usize = 4;
|
||||||
|
pub const PAGE_SIZE: usize = 2048;
|
||||||
|
|
|
||||||
309
src/file_storage.rs
Normal file
309
src/file_storage.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
use bincode::error::DecodeError;
|
||||||
|
|
||||||
|
use crate::storage::Storage;
|
||||||
|
use crate::{config::PAGE_SIZE, node::Node};
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FileStorage {
|
||||||
|
pub file: File,
|
||||||
|
pub page_reads: usize,
|
||||||
|
pub page_writes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileStorage {
|
||||||
|
pub fn new(path: &str) -> Self {
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(path)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
file,
|
||||||
|
page_reads: 0,
|
||||||
|
page_writes: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage for FileStorage {
|
||||||
|
fn read_node(&mut self, loc: usize) -> Option<Node> {
|
||||||
|
let offset = (loc * PAGE_SIZE) as u64;
|
||||||
|
self.file.seek(SeekFrom::Start(offset)).ok()?;
|
||||||
|
let mut block = [0u8; PAGE_SIZE];
|
||||||
|
self.file.read_exact(&mut block).ok()?;
|
||||||
|
self.page_reads += 1;
|
||||||
|
FileStorage::deserialize_node(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_node(&mut self, loc: usize, input: &Node) {
|
||||||
|
let offset = (loc * PAGE_SIZE) as u64;
|
||||||
|
let block = FileStorage::serialize_node(input);
|
||||||
|
self.file.seek(SeekFrom::Start(offset)).unwrap();
|
||||||
|
self.file.write_all(&block).unwrap();
|
||||||
|
self.page_writes += 1;
|
||||||
|
}
|
||||||
|
fn total_nodes(&self) -> usize {
|
||||||
|
self.file.metadata().unwrap().len() as usize / PAGE_SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileStorage {
|
||||||
|
fn serialize_node(input: &Node) -> [u8; PAGE_SIZE] {
|
||||||
|
let mut slice = [0u8; PAGE_SIZE];
|
||||||
|
let _ = bincode::encode_into_slice(input, &mut slice, bincode::config::standard());
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
fn deserialize_node(input: [u8; PAGE_SIZE]) -> Option<Node> {
|
||||||
|
let result: Result<(Node, usize), DecodeError> =
|
||||||
|
bincode::decode_from_slice(&input, bincode::config::standard());
|
||||||
|
match result {
|
||||||
|
Ok(value) => Some(value.0),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
config::MAX_KEYS,
|
||||||
|
node::{InternalNode, LeafNode, Node},
|
||||||
|
record::Record,
|
||||||
|
};
|
||||||
|
use proptest::prelude::*;
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
|
||||||
|
fn temp_storage() -> FileStorage {
|
||||||
|
let file = NamedTempFile::new().unwrap();
|
||||||
|
FileStorage::new(file.path().to_str().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_leaf() -> Node {
|
||||||
|
Node::Leaf(LeafNode {
|
||||||
|
keys: vec![1, 2, 3],
|
||||||
|
values: vec![[1; 7], [2; 7], [3; 7]],
|
||||||
|
next: Some(42),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_internal() -> Node {
|
||||||
|
Node::Internal(InternalNode {
|
||||||
|
keys: vec![10, 20],
|
||||||
|
children: vec![1, 2, 3],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_and_read_leaf_node() {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
let node = sample_leaf();
|
||||||
|
|
||||||
|
storage.write_node(0, &node);
|
||||||
|
let read_back = storage.read_node(0);
|
||||||
|
|
||||||
|
assert_eq!(read_back.unwrap(), node);
|
||||||
|
assert_eq!(storage.page_reads, 1);
|
||||||
|
assert_eq!(storage.page_writes, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_and_read_internal_node() {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
let node = sample_internal();
|
||||||
|
|
||||||
|
storage.write_node(5, &node);
|
||||||
|
let read_back = storage.read_node(5);
|
||||||
|
|
||||||
|
assert_eq!(read_back.unwrap(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_empty_page_returns_none() {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
let result = storage.read_node(0);
|
||||||
|
|
||||||
|
assert!(result.is_none());
|
||||||
|
assert_eq!(storage.page_reads, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn total_nodes_is_correct() {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
storage.write_node(0, &sample_leaf());
|
||||||
|
storage.write_node(1, &sample_internal());
|
||||||
|
|
||||||
|
assert_eq!(storage.total_nodes(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overwrite_existing_node() {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
let node1 = sample_leaf();
|
||||||
|
let node2 = sample_internal();
|
||||||
|
|
||||||
|
storage.write_node(0, &node1);
|
||||||
|
storage.write_node(0, &node2);
|
||||||
|
|
||||||
|
let read_back = storage.read_node(0).unwrap();
|
||||||
|
assert_eq!(read_back, node2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arb_record() -> impl Strategy<Value = Record> {
|
||||||
|
prop::array::uniform7(any::<i32>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arb_node() -> impl Strategy<Value = Node> {
|
||||||
|
let max = MAX_KEYS * 2 + 2;
|
||||||
|
|
||||||
|
prop_oneof![
|
||||||
|
(
|
||||||
|
prop::collection::vec(any::<i32>(), 0..=max),
|
||||||
|
prop::collection::vec(arb_record(), 0..=max),
|
||||||
|
proptest::option::of(any::<usize>()),
|
||||||
|
)
|
||||||
|
.prop_map(|(keys, values, next)| { Node::Leaf(LeafNode { keys, values, next }) }),
|
||||||
|
(
|
||||||
|
prop::collection::vec(any::<i32>(), 0..=max),
|
||||||
|
prop::collection::vec(any::<usize>(), 0..=max + 1),
|
||||||
|
)
|
||||||
|
.prop_map(|(keys, children)| { Node::Internal(InternalNode { keys, children }) })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arb_nodes() -> impl Strategy<Value = Vec<Node>> {
|
||||||
|
prop::collection::vec(arb_node(), 0..100)
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn node_roundtrip(node in arb_node()) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
storage.write_node(0, &node);
|
||||||
|
let read_back = storage.read_node(0).unwrap();
|
||||||
|
|
||||||
|
prop_assert_eq!(node, read_back);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_multiple_nodes_roundtrip(nodes in arb_nodes()) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
storage.write_node(i, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
let read_back = storage.read_node(i).unwrap();
|
||||||
|
prop_assert_eq!(read_back, node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_overwrite_last_wins(nodes in arb_nodes()) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
for node in &nodes {
|
||||||
|
storage.write_node(0, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(last) = nodes.last() {
|
||||||
|
let read_back = storage.read_node(0).unwrap();
|
||||||
|
prop_assert_eq!(read_back, last.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_sparse_writes_are_isolated(
|
||||||
|
a in arb_node(),
|
||||||
|
b in arb_node(),
|
||||||
|
loc_a in 0usize..20,
|
||||||
|
loc_b in 0usize..20,
|
||||||
|
) {
|
||||||
|
prop_assume!(loc_a != loc_b);
|
||||||
|
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
storage.write_node(loc_a, &a);
|
||||||
|
storage.write_node(loc_b, &b);
|
||||||
|
|
||||||
|
let ra = storage.read_node(loc_a).unwrap();
|
||||||
|
let rb = storage.read_node(loc_b).unwrap();
|
||||||
|
|
||||||
|
prop_assert_eq!(ra, a);
|
||||||
|
prop_assert_eq!(rb, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_total_nodes_matches_highest_write(
|
||||||
|
nodes in arb_nodes()
|
||||||
|
) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
storage.write_node(i * 2, node); // deliberately sparse
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nodes.is_empty() {
|
||||||
|
let expected_min = (nodes.len() - 1) * 2 + 1;
|
||||||
|
prop_assert!(storage.total_nodes() >= expected_min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_reads_do_not_mutate_data(nodes in arb_nodes()) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
storage.write_node(i, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..nodes.len() {
|
||||||
|
let _ = storage.read_node(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
let read_back = storage.read_node(i).unwrap();
|
||||||
|
prop_assert_eq!(read_back, node.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn prop_unwritten_pages_return_none(
|
||||||
|
nodes in arb_nodes(),
|
||||||
|
extra in 1usize..20
|
||||||
|
) {
|
||||||
|
let mut storage = temp_storage();
|
||||||
|
|
||||||
|
for (i, node) in nodes.iter().enumerate() {
|
||||||
|
storage.write_node(i, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
let loc = nodes.len() + extra;
|
||||||
|
let result = storage.read_node(loc);
|
||||||
|
|
||||||
|
prop_assert!(result.is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
mod btree;
|
mod btree;
|
||||||
mod config;
|
mod config;
|
||||||
|
mod file_storage;
|
||||||
mod node;
|
mod node;
|
||||||
mod record;
|
mod record;
|
||||||
mod storage;
|
mod storage;
|
||||||
use crate::btree::BPlusTree;
|
use crate::{btree::BPlusTree, file_storage::FileStorage};
|
||||||
use crate::storage::InMemoryStorage;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let storage = InMemoryStorage::new();
|
let storage = FileStorage::new("data.db");
|
||||||
let mut tree = BPlusTree::open(storage);
|
let mut tree = BPlusTree::open(storage);
|
||||||
|
|
||||||
// Insert some key-value pairs
|
// Insert some key-value pairs
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
use bincode::{Decode, Encode};
|
||||||
|
|
||||||
use crate::config::DEGREE;
|
use crate::config::DEGREE;
|
||||||
use crate::record::Record;
|
use crate::record::Record;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
Leaf(LeafNode),
|
Leaf(LeafNode),
|
||||||
Internal(InternalNode),
|
Internal(InternalNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
pub struct LeafNode {
|
pub struct LeafNode {
|
||||||
pub keys: Vec<i32>,
|
pub keys: Vec<i32>,
|
||||||
pub values: Vec<Record>,
|
pub values: Vec<Record>,
|
||||||
|
|
@ -24,7 +26,7 @@ impl LeafNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
|
||||||
pub struct InternalNode {
|
pub struct InternalNode {
|
||||||
pub keys: Vec<i32>,
|
pub keys: Vec<i32>,
|
||||||
pub children: Vec<usize>,
|
pub children: Vec<usize>,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
pub trait Storage {
|
pub trait Storage {
|
||||||
fn read_node(&self, loc: usize) -> Option<Node>;
|
fn read_node(&mut self, loc: usize) -> Option<Node>;
|
||||||
fn write_node(&mut self, loc: usize, node: &Node);
|
fn write_node(&mut self, loc: usize, node: &Node);
|
||||||
fn total_nodes(&self) -> usize;
|
fn total_nodes(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ impl InMemoryStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage for InMemoryStorage {
|
impl Storage for InMemoryStorage {
|
||||||
fn read_node(&self, loc: usize) -> Option<Node> {
|
fn read_node(&mut self, loc: usize) -> Option<Node> {
|
||||||
self.nodes.get(loc)?.clone()
|
self.nodes.get(loc)?.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue