This commit is contained in:
bronku 2025-12-09 11:33:33 +01:00
parent 4d3fdcb356
commit 5f533af1c8
9 changed files with 414 additions and 68 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
/target /target
.DS_Store
btree.idx

122
Cargo.lock generated
View file

@ -3,31 +3,17 @@
version = 4 version = 4
[[package]] [[package]]
name = "bincode" name = "bitflags"
version = "2.0.1" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
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]] [[package]]
name = "btree" name = "btree"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode",
"rand", "rand",
"tempfile",
] ]
[[package]] [[package]]
@ -36,6 +22,22 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.3.4"
@ -54,6 +56,18 @@ version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" 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]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@ -117,32 +131,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "serde" name = "rustix"
version = "1.0.228" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [ dependencies = [
"serde_core", "bitflags",
] "errno",
"libc",
[[package]] "linux-raw-sys",
name = "serde_core" "windows-sys",
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]]
@ -156,24 +154,25 @@ dependencies = [
"unicode-ident", "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]] [[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]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.1+wasi-0.2.4" version = "1.0.1+wasi-0.2.4"
@ -183,6 +182,21 @@ dependencies = [
"wit-bindgen", "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]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.46.0" version = "0.46.0"

View file

@ -4,5 +4,5 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
bincode = "2.0.1"
rand = "0.9.2" rand = "0.9.2"
tempfile = "3.23.0"

View file

@ -1,6 +1,36 @@
use crate::Record;
use crate::node::Node; 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)] #[derive(Debug)]
pub struct BTree { pub struct BTree {
root: Option<Box<Node>>, root: Option<Box<Node>>,
@ -61,3 +91,4 @@ impl BTree {
} }
} }
} }
*/

2
src/consts.rs Normal file
View 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

View file

@ -1,11 +1,35 @@
mod btree; mod btree;
mod consts;
mod node; mod node;
mod node_storage;
mod record; mod record;
use btree::BTree; use crate::node::Node;
use record::Record; use crate::node_storage::NodeStorage;
use crate::record::Record;
fn main() { 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 btree = BTree::new();
let mut values: Vec<i32> = vec![]; let mut values: Vec<i32> = vec![];
@ -24,4 +48,5 @@ fn main() {
println!("Search for 6: {:?}", btree.search(values[0])); println!("Search for 6: {:?}", btree.search(values[0]));
println!("Search for 15: {:?}", btree.search(values[4])); println!("Search for 15: {:?}", btree.search(values[4]));
println!("Search for 21: {:?}", btree.search(100)); println!("Search for 21: {:?}", btree.search(100));
*/
} }

View file

@ -1,23 +1,36 @@
use crate::consts::*;
use crate::record::Record; use crate::record::Record;
const MIN_DEGREE: usize = 3; // Minimum degree (minimum children per node) #[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub struct Node { pub struct Node {
pub keys: Vec<Record>, pub keys: [Option<Record>; MAX_KEYS],
pub children: Vec<Box<Node>>, pub children: [Option<usize>; MAX_KEYS + 1],
pub num_keys: usize,
pub is_leaf: bool, pub is_leaf: bool,
} }
impl Node { impl Node {
pub fn new(is_leaf: bool) -> Self { pub fn new(is_leaf: bool) -> Self {
Node { Self {
keys: Vec::new(), keys: [(); MAX_KEYS].map(|_| None),
children: Vec::new(), children: [(); MAX_KEYS + 1].map(|_| None),
num_keys: 0,
is_leaf, 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 { pub fn is_full(&self) -> bool {
self.keys.len() >= 2 * MIN_DEGREE - 1 self.keys.len() >= 2 * MIN_DEGREE - 1
} }
@ -82,4 +95,5 @@ impl Node {
self.keys.insert(i, median); self.keys.insert(i, median);
self.children.insert(i + 1, new_child); self.children.insert(i + 1, new_child);
} }
*/
} }

242
src/node_storage.rs Normal file
View 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);
}
}

View file

@ -1,7 +1,6 @@
use bincode::{Decode, Encode};
use rand::Rng; 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 struct Record {
pub key: i32, pub key: i32,
pub a: [i32; 5], pub a: [i32; 5],
@ -27,4 +26,21 @@ impl Record {
let x = rng.random::<i32>(); let x = rng.random::<i32>();
Self::new(a, x) 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],
}
}
} }