From c8209665bebfd1d83b081e274b8d9fc3e346a685 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Tue, 1 Oct 2024 20:58:32 +0300 Subject: Add DB builder pattern --- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 11 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 78791bc..8a2aab5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod log_db { use priority_queue::PriorityQueue; use rev_buf_reader::RevBufReader; use std::collections::{BTreeMap, HashSet}; + use std::error::Error; use std::fmt::Debug; use std::fs::{self, metadata, File}; use std::io::{self, BufRead, Read, Seek, Write}; @@ -160,26 +161,125 @@ pub mod log_db { } } - #[derive(Clone)] - pub struct Config { + #[derive(Debug, Clone, Eq, PartialEq)] + pub enum MemtableEvictPolicy { + LeastWritten, + LeastRead, + LeastReadOrWritten, + } + + pub struct ConfigBuilder<'a, Field: Eq + Clone + Debug> { + data_dir: Option, + segment_size: Option, + memtable_capacity: Option, + fields: Option<&'a Vec<(Field, RecordFieldType)>>, + primary_key: Option, + secondary_keys: Option>, + memtable_evict_policy: Option, + } + + impl<'a, Field: Eq + Clone + Debug> ConfigBuilder<'a, Field> { + pub fn new() -> ConfigBuilder<'a, Field> { + ConfigBuilder:: { + data_dir: None, + segment_size: None, + memtable_capacity: None, + fields: None, + primary_key: None, + secondary_keys: None, + memtable_evict_policy: None, + } + } + /// Directory where the database will store its data. - pub data_dir: String, + pub fn data_dir(&mut self, data_dir: &str) -> &mut Self { + self.data_dir = Some(data_dir.to_string()); + self + } + /// The maximum size of a segment file in bytes. /// Once a segment file reaches this size, it is closed and a new one is created. /// Closed segment files can be compacted. - pub segment_size: usize, - /// The maximum size of a single memtable in bytes. + pub fn segment_size(&mut self, segment_size: usize) -> &mut Self { + self.segment_size = Some(segment_size); + self + } + + /// The maximum size of a single memtable in terms of records. /// Note that each secondary index will have its own memtable. - pub memtable_size: usize, + pub fn memtable_capacity(&mut self, memtable_capacity: usize) -> &mut Self { + self.memtable_capacity = Some(memtable_capacity); + self + } + /// The field schema of the database. - pub fields: Vec<(Field, RecordFieldType)>, + pub fn fields(&mut self, fields: &'a Vec<(Field, RecordFieldType)>) -> &mut Self { + self.fields = Some(fields); + self + } + /// The primary key of the database, used to construct /// the primary memtable index. This should be the field /// that is most frequently queried. - pub primary_key: Field, + pub fn primary_key(&mut self, primary_key: Field) -> &mut Self { + self.primary_key = Some(primary_key); + self + } + /// The secondary keys of the database, used to construct /// the secondary memtable indexes. + pub fn secondary_keys(&mut self, secondary_keys: Vec) -> &mut Self { + self.secondary_keys = Some(secondary_keys); + self + } + + /// The eviction policy for the memtables. Determines which + /// record will be dropped from a memtable when it reaches + /// capacity. + pub fn memtable_evict_policy( + &mut self, + memtable_evict_policy: MemtableEvictPolicy, + ) -> &mut Self { + self.memtable_evict_policy = Some(memtable_evict_policy); + self + } + + pub fn initialize(&self) -> Result, io::Error> { + let config = Config:: { + data_dir: self.data_dir.clone().unwrap_or("db_data".to_string()), + segment_size: self.segment_size.unwrap_or(4 * 1024 * 1024), // 4MB + memtable_capacity: self.memtable_capacity.unwrap_or(1_000_000), + fields: self + .fields + .ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + "Required config value \"fields\" is not set", + ))? + .clone(), + primary_key: self.primary_key.clone().ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + "Required config value \"primary_key\" is not set", + ))?, + secondary_keys: self.secondary_keys.clone().unwrap_or(Vec::new()), + memtable_evict_policy: self + .memtable_evict_policy + .clone() + .unwrap_or(MemtableEvictPolicy::LeastReadOrWritten), + }; + + DB::initialize(&config) + } + } + + #[derive(Clone)] + struct Config { + pub data_dir: String, + pub segment_size: usize, + pub memtable_capacity: usize, + pub fields: Vec<(Field, RecordFieldType)>, + pub primary_key: Field, pub secondary_keys: Vec, + pub memtable_evict_policy: MemtableEvictPolicy, } pub struct DB { @@ -190,7 +290,12 @@ pub mod log_db { } impl DB { - pub fn initialize(config: &Config) -> Result, io::Error> { + /// Create a new database configuration builder. + pub fn configure() -> ConfigBuilder<'static, Field> { + ConfigBuilder::new() + } + + fn initialize(config: &Config) -> Result, io::Error> { info!("Initializing DB"); // If data_dir does not exist, create it if !fs::exists(&config.data_dir)? { @@ -337,7 +442,7 @@ pub mod log_db { "Primary key must be an IndexableValue", ))?; - if self.primary_memtable.len() < self.config.memtable_size { + if self.primary_memtable.len() < self.config.memtable_capacity { // TODO: handle capacity better, remove oldest records debug!( "Inserting record into primary memtable with key {:?} = {:?}", @@ -430,7 +535,7 @@ pub mod log_db { debug!("Found matching record in log file"); - if self.primary_memtable.len() < self.config.memtable_size { + if self.primary_memtable.len() < self.config.memtable_capacity { if *field == self.config.primary_key { // TODO: handle capacity better, remove oldest records debug!( -- cgit v1.3