storage?
This commit is contained in:
parent
4d3fdcb356
commit
5f533af1c8
9 changed files with 414 additions and 68 deletions
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