storage?
This commit is contained in:
parent
4d3fdcb356
commit
5f533af1c8
9 changed files with 414 additions and 68 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
|||
/target
|
||||
.DS_Store
|
||||
btree.idx
|
||||
|
|
|
|||
122
Cargo.lock
generated
122
Cargo.lock
generated
|
|
@ -3,31 +3,17 @@
|
|||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
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",
|
||||
]
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "btree"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"rand",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -36,6 +22,22 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
|
|
@ -54,6 +56,18 @@ version = "0.2.178"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
|
@ -117,32 +131,16 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
name = "rustix"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
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",
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -156,24 +154,25 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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 = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
|
|
@ -183,6 +182,21 @@ dependencies = [
|
|||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bincode = "2.0.1"
|
||||
rand = "0.9.2"
|
||||
tempfile = "3.23.0"
|
||||
|
|
|
|||
33
src/btree.rs
33
src/btree.rs
|
|
@ -1,6 +1,36 @@
|
|||
use crate::Record;
|
||||
use crate::node::Node;
|
||||
use crate::record::Record;
|
||||
use crate::node_storage::NodeStorage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BTree {
|
||||
storage: NodeStorage,
|
||||
root: Node,
|
||||
}
|
||||
|
||||
impl BTree {
|
||||
pub fn new(filename: &str) -> Self {
|
||||
let mut storage = NodeStorage::open(filename);
|
||||
let root = if storage.num_nodes() > 0 {
|
||||
storage.read_node(0)
|
||||
} else {
|
||||
Node::new(true)
|
||||
};
|
||||
|
||||
BTree {
|
||||
storage: storage,
|
||||
root: root,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(&self, key: i32) -> Option<Record> {
|
||||
let mut current = 0;
|
||||
let mut node = self.root;
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
/*
|
||||
#[derive(Debug)]
|
||||
pub struct BTree {
|
||||
root: Option<Box<Node>>,
|
||||
|
|
@ -61,3 +91,4 @@ impl BTree {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
2
src/consts.rs
Normal file
2
src/consts.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub const MIN_DEGREE: usize = 3; // Example minimum degree
|
||||
pub const MAX_KEYS: usize = 2 * MIN_DEGREE; // Max keys per node
|
||||
29
src/main.rs
29
src/main.rs
|
|
@ -1,11 +1,35 @@
|
|||
mod btree;
|
||||
mod consts;
|
||||
mod node;
|
||||
mod node_storage;
|
||||
mod record;
|
||||
|
||||
use btree::BTree;
|
||||
use record::Record;
|
||||
use crate::node::Node;
|
||||
use crate::node_storage::NodeStorage;
|
||||
use crate::record::Record;
|
||||
|
||||
fn main() {
|
||||
let mut storage = NodeStorage::open("btree.idx");
|
||||
|
||||
let mut node = storage.read_node(1);
|
||||
node.insert_non_full(Record {
|
||||
key: 1,
|
||||
a: [69, 0, 0, 0, 0],
|
||||
x: 0,
|
||||
});
|
||||
|
||||
println!("{:?}", node);
|
||||
|
||||
storage.write_node(1, &node);
|
||||
|
||||
let node1 = storage.read_node(1);
|
||||
|
||||
println!("{:?}", node1);
|
||||
|
||||
println!("Record = {} bytes", std::mem::size_of::<Record>());
|
||||
println!("Node = {} bytes", std::mem::size_of::<Node>());
|
||||
|
||||
/*
|
||||
let mut btree = BTree::new();
|
||||
|
||||
let mut values: Vec<i32> = vec![];
|
||||
|
|
@ -24,4 +48,5 @@ fn main() {
|
|||
println!("Search for 6: {:?}", btree.search(values[0]));
|
||||
println!("Search for 15: {:?}", btree.search(values[4]));
|
||||
println!("Search for 21: {:?}", btree.search(100));
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
30
src/node.rs
30
src/node.rs
|
|
@ -1,23 +1,36 @@
|
|||
use crate::consts::*;
|
||||
use crate::record::Record;
|
||||
|
||||
const MIN_DEGREE: usize = 3; // Minimum degree (minimum children per node)
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Node {
|
||||
pub keys: Vec<Record>,
|
||||
pub children: Vec<Box<Node>>,
|
||||
pub keys: [Option<Record>; MAX_KEYS],
|
||||
pub children: [Option<usize>; MAX_KEYS + 1],
|
||||
pub num_keys: usize,
|
||||
pub is_leaf: bool,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new(is_leaf: bool) -> Self {
|
||||
Node {
|
||||
keys: Vec::new(),
|
||||
children: Vec::new(),
|
||||
Self {
|
||||
keys: [(); MAX_KEYS].map(|_| None),
|
||||
children: [(); MAX_KEYS + 1].map(|_| None),
|
||||
num_keys: 0,
|
||||
is_leaf,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_non_full(&mut self, input: Record) -> bool {
|
||||
let i = self.num_keys;
|
||||
if i == MAX_KEYS {
|
||||
return false;
|
||||
}
|
||||
self.keys[i] = Some(input);
|
||||
self.num_keys += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.keys.len() >= 2 * MIN_DEGREE - 1
|
||||
}
|
||||
|
|
@ -82,4 +95,5 @@ impl Node {
|
|||
self.keys.insert(i, median);
|
||||
self.children.insert(i + 1, new_child);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
242
src/node_storage.rs
Normal file
242
src/node_storage.rs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::node::Node;
|
||||
use crate::record::Record;
|
||||
|
||||
pub const PAGE_SIZE: usize = 512;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeStorage {
|
||||
file: File,
|
||||
pub page_reads: usize,
|
||||
pub page_writes: usize,
|
||||
}
|
||||
|
||||
impl NodeStorage {
|
||||
pub fn open(path: &str) -> Self {
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
file,
|
||||
page_reads: 0,
|
||||
page_writes: 0,
|
||||
}
|
||||
}
|
||||
fn serialize_node(node: &Node) -> [u8; PAGE_SIZE] {
|
||||
let mut out = String::new();
|
||||
|
||||
// Format:
|
||||
// L|numkeys|key0;key1;...|child0,child1,...
|
||||
out.push(if node.is_leaf { 'L' } else { 'I' });
|
||||
out.push('|');
|
||||
out.push_str(&node.num_keys.to_string());
|
||||
out.push('|');
|
||||
|
||||
// Keys
|
||||
for i in 0..node.num_keys {
|
||||
let rec = node.keys[i].unwrap();
|
||||
out.push_str(&rec.to_text());
|
||||
if i + 1 < node.num_keys {
|
||||
out.push(';');
|
||||
}
|
||||
}
|
||||
|
||||
out.push('|');
|
||||
|
||||
// Children (m = num_keys, m+1 children)
|
||||
for i in 0..=node.num_keys {
|
||||
match node.children[i] {
|
||||
Some(idx) => out.push_str(&idx.to_string()),
|
||||
None => out.push('.'),
|
||||
}
|
||||
|
||||
if i < node.num_keys {
|
||||
out.push(',');
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to fixed-size block
|
||||
let mut block = [b' '; PAGE_SIZE];
|
||||
let bytes = out.as_bytes();
|
||||
assert!(bytes.len() <= PAGE_SIZE, "Node too large to serialize");
|
||||
|
||||
block[..bytes.len()].copy_from_slice(bytes);
|
||||
block
|
||||
}
|
||||
|
||||
fn deserialize_node(block: &[u8; PAGE_SIZE]) -> Node {
|
||||
let text = std::str::from_utf8(block).unwrap().trim_end();
|
||||
let parts: Vec<&str> = text.split('|').collect();
|
||||
|
||||
let mut node = Node::new(parts[0] == "L");
|
||||
node.num_keys = parts[1].parse().unwrap();
|
||||
|
||||
if !parts[2].is_empty() {
|
||||
for (i, ks) in parts[2].split(';').enumerate() {
|
||||
let rec = Record::from_text(ks);
|
||||
node.keys[i] = Some(rec);
|
||||
}
|
||||
}
|
||||
|
||||
if !parts[3].is_empty() {
|
||||
for (i, cs) in parts[3].split(',').enumerate() {
|
||||
node.children[i] = if cs == "." {
|
||||
None
|
||||
} else {
|
||||
Some(cs.parse().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
pub fn read_node(&mut self, index: usize) -> Node {
|
||||
let offset = (index * PAGE_SIZE) as u64;
|
||||
|
||||
self.file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
let mut block = [0u8; PAGE_SIZE];
|
||||
self.file.read_exact(&mut block).unwrap();
|
||||
|
||||
self.page_reads += 1;
|
||||
|
||||
Self::deserialize_node(&block)
|
||||
}
|
||||
|
||||
pub fn write_node(&mut self, index: usize, node: &Node) {
|
||||
let offset = (index * PAGE_SIZE) as u64;
|
||||
|
||||
let block = Self::serialize_node(node);
|
||||
|
||||
self.file.seek(SeekFrom::Start(offset)).unwrap();
|
||||
self.file.write_all(&block).unwrap();
|
||||
|
||||
self.page_writes += 1;
|
||||
}
|
||||
|
||||
pub fn append_node(&mut self, node: &Node) -> usize {
|
||||
let index = self.num_nodes();
|
||||
self.write_node(index, node);
|
||||
index
|
||||
}
|
||||
|
||||
pub fn num_nodes(&self) -> usize {
|
||||
let len = self.file.metadata().unwrap().len() as usize;
|
||||
len / PAGE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::node::Node;
|
||||
use crate::record::Record;
|
||||
use rand::Rng;
|
||||
use tempfile::tempfile;
|
||||
|
||||
fn random_node() -> Node {
|
||||
let mut node = Node::new(true);
|
||||
let mut rng = rand::rng();
|
||||
let n = rng.random_range(1..=crate::consts::MAX_KEYS);
|
||||
node.num_keys = n;
|
||||
for i in 0..n {
|
||||
node.keys[i] = Some(Record::random());
|
||||
}
|
||||
for i in 0..=n {
|
||||
node.children[i] = Some(rng.random_range(0..100));
|
||||
}
|
||||
|
||||
node
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append_and_read_single_node() {
|
||||
let file = tempfile().unwrap();
|
||||
let mut storage = NodeStorage {
|
||||
file,
|
||||
page_reads: 0,
|
||||
page_writes: 0,
|
||||
};
|
||||
|
||||
let node = random_node();
|
||||
let index = storage.append_node(&node);
|
||||
|
||||
assert_eq!(index, 0);
|
||||
assert_eq!(storage.page_writes, 1);
|
||||
assert_eq!(storage.num_nodes(), 1);
|
||||
|
||||
let read_node = storage.read_node(0);
|
||||
assert_eq!(read_node.num_keys, node.num_keys);
|
||||
|
||||
for i in 0..node.num_keys {
|
||||
assert_eq!(read_node.keys[i].unwrap().key, node.keys[i].unwrap().key);
|
||||
}
|
||||
assert_eq!(storage.page_reads, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overwrite_node() {
|
||||
let file = tempfile().unwrap();
|
||||
let mut storage = NodeStorage {
|
||||
file,
|
||||
page_reads: 0,
|
||||
page_writes: 0,
|
||||
};
|
||||
|
||||
let node1 = random_node();
|
||||
let node2 = random_node();
|
||||
|
||||
let index = storage.append_node(&node1);
|
||||
storage.write_node(index, &node2);
|
||||
|
||||
let read_node = storage.read_node(index);
|
||||
assert_eq!(read_node.num_keys, node2.num_keys);
|
||||
|
||||
for i in 0..node2.num_keys {
|
||||
assert_eq!(read_node.keys[i].unwrap().key, node2.keys[i].unwrap().key);
|
||||
}
|
||||
|
||||
assert_eq!(storage.page_writes, 2);
|
||||
assert_eq!(storage.page_reads, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_nodes() {
|
||||
let file = tempfile().unwrap();
|
||||
let mut storage = NodeStorage {
|
||||
file,
|
||||
page_reads: 0,
|
||||
page_writes: 0,
|
||||
};
|
||||
|
||||
let mut nodes = vec![];
|
||||
|
||||
// append 10 random nodes
|
||||
for _ in 0..10 {
|
||||
let node = random_node();
|
||||
storage.append_node(&node);
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
assert_eq!(storage.num_nodes(), 10);
|
||||
assert_eq!(storage.page_writes, 10);
|
||||
|
||||
// read them back and verify
|
||||
for i in 0..10 {
|
||||
let read_node = storage.read_node(i);
|
||||
let orig = &nodes[i];
|
||||
assert_eq!(read_node.num_keys, orig.num_keys);
|
||||
for j in 0..orig.num_keys {
|
||||
assert_eq!(read_node.keys[j].unwrap().key, orig.keys[j].unwrap().key);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(storage.page_reads, 10);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use bincode::{Decode, Encode};
|
||||
use rand::Rng;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Encode, Decode)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
pub struct Record {
|
||||
pub key: i32,
|
||||
pub a: [i32; 5],
|
||||
|
|
@ -27,4 +26,21 @@ impl Record {
|
|||
let x = rng.random::<i32>();
|
||||
Self::new(a, x)
|
||||
}
|
||||
|
||||
pub fn to_text(&self) -> String {
|
||||
format!(
|
||||
"{},{},{},{},{},{},{}",
|
||||
self.key, self.a[0], self.a[1], self.a[2], self.a[3], self.a[4], self.x
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_text(s: &str) -> Self {
|
||||
let parts: Vec<i32> = s.split(',').map(|p| p.parse().unwrap()).collect();
|
||||
|
||||
Self {
|
||||
key: parts[0],
|
||||
a: [parts[1], parts[2], parts[3], parts[4], parts[5]],
|
||||
x: parts[6],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue