From a4b813881a47bb9d8aa528a9598cfdb052ae231e Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Mon, 6 Jan 2025 19:28:41 +0200 Subject: Move primary_key and secondary_keys configuration to Recordable --- log_db/src/common.rs | 2 ++ log_db/src/lib.rs | 62 ++++++++++++++++++--------------------------- log_db/src/record.rs | 9 ++++++- log_db/tests/integration.rs | 34 +++++++++++++------------ 4 files changed, 52 insertions(+), 55 deletions(-) diff --git a/log_db/src/common.rs b/log_db/src/common.rs index a3ff5c2..4f5751d 100644 --- a/log_db/src/common.rs +++ b/log_db/src/common.rs @@ -242,6 +242,7 @@ impl Display for WriteDurability { #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum IndexableValue { + Null, Int(i64), String(String), } @@ -393,6 +394,7 @@ impl Value { pub fn as_indexable(&self) -> Option { match self { + Value::Null => Some(IndexableValue::Null), Value::Int(i) => Some(IndexableValue::Int(*i)), Value::String(s) => Some(IndexableValue::String(s.clone())), _ => None, diff --git a/log_db/src/lib.rs b/log_db/src/lib.rs index 2cfcfbe..0709a8e 100644 --- a/log_db/src/lib.rs +++ b/log_db/src/lib.rs @@ -22,26 +22,25 @@ use std::collections::BTreeMap; use std::fmt::Debug; use std::fs::{self}; use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::marker::PhantomData; use std::path::{Path, PathBuf}; pub struct ConfigBuilder { data_dir: Option, segment_size: Option, - primary_key: Option, - secondary_keys: Option>, write_durability: Option, read_consistency: Option, + _marker: PhantomData, } -impl<'a, R: Recordable> ConfigBuilder { +impl ConfigBuilder { pub fn new() -> ConfigBuilder { - ConfigBuilder:: { + ConfigBuilder { data_dir: None, segment_size: None, - primary_key: None, - secondary_keys: None, write_durability: None, read_consistency: None, + _marker: PhantomData, } } @@ -60,21 +59,6 @@ impl<'a, R: Recordable> ConfigBuilder { 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 fn primary_key(&mut self, primary_key: R::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: &[R::Field]) -> &mut Self { - self.secondary_keys = Some(secondary_keys.to_vec()); - self - } - /// The write durability policy for the database. /// This determines how writes are persisted to disk. /// The default is WriteDurability::Flush. @@ -93,15 +77,12 @@ impl<'a, R: Recordable> ConfigBuilder { } pub fn initialize(&self) -> Result, DBError> { - let config = Config:: { + let config = Config { + fields: R::schema(), + primary_key: R::primary_key(), + secondary_keys: R::secondary_keys(), data_dir: self.data_dir.clone().unwrap_or("db_data".to_string()), segment_size: self.segment_size.unwrap_or(4 * 1024 * 1024), // 4MB - fields: R::schema(), - 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()), write_durability: self .write_durability .clone() @@ -118,17 +99,18 @@ impl<'a, R: Recordable> ConfigBuilder { #[derive(Clone)] struct Config { - pub data_dir: String, - pub segment_size: usize, pub fields: Vec<(R::Field, ValueType)>, pub primary_key: R::Field, pub secondary_keys: Vec, + pub data_dir: String, + pub segment_size: usize, pub write_durability: WriteDurability, pub read_consistency: ReadConsistency, } pub struct DB { config: Config, + data_dir: PathBuf, active_metadata_file: fs::File, active_data_file: fs::File, @@ -879,6 +861,12 @@ mod tests { impl Recordable for TestInst1 { type Field = Field; + fn schema() -> Vec<(Field, ValueType)> { + vec![(Field::Id, ValueType::int())] + } + fn primary_key() -> Self::Field { + Field::Id + } fn into_record(self) -> Vec { vec![Value::Int(self.id)] @@ -893,10 +881,6 @@ mod tests { }, } } - - fn schema() -> Vec<(Field, ValueType)> { - vec![(Field::Id, ValueType::int())] - } } struct TestInst2 { @@ -906,6 +890,12 @@ mod tests { impl Recordable for TestInst2 { type Field = Field; + fn primary_key() -> Self::Field { + Field::Id + } + fn secondary_keys() -> Vec { + vec![Field::Name] + } fn into_record(self) -> Vec { vec![Value::Int(self.id), Value::String(self.name)] @@ -944,7 +934,6 @@ mod tests { let mut db = DB::::configure() .data_dir(data_dir.to_str().unwrap()) .segment_size(segment_size) - .primary_key(Field::Id) .initialize() .expect("Failed to create DB"); @@ -1028,7 +1017,6 @@ mod tests { let mut db = DB::::configure() .data_dir(data_dir.to_str().unwrap()) - .primary_key(Field::Id) .initialize() .expect("Failed to create DB"); @@ -1080,8 +1068,6 @@ mod tests { let mut db = DB::::configure() .data_dir(data_dir.to_str().unwrap()) - .primary_key(Field::Id) - .secondary_keys(&[Field::Name]) .initialize() .expect("Failed to create DB"); diff --git a/log_db/src/record.rs b/log_db/src/record.rs index 1b882ed..8d518f0 100644 --- a/log_db/src/record.rs +++ b/log_db/src/record.rs @@ -104,8 +104,15 @@ impl Record { pub trait Recordable { /// The field type of the data structure implementing the `Recordable` trait. type Field: Eq + Clone + Debug; - /// Define the schema of the data structure implementing the `Recordable` trait. + /// Define the schema of the instance implementing the `Recordable` trait. fn schema() -> Vec<(Self::Field, ValueType)>; + /// Define the primary key of the instance implementing the `Recordable` trait. + fn primary_key() -> Self::Field; + /// Define the secondary keys of the instance implementing the `Recordable` trait. + fn secondary_keys() -> Vec { + Vec::new() + } + /// Convert the data structure implementing the `Recordable` trait into a vector of database values. fn into_record(self) -> Vec; /// Convert a vector of database values into the data structure implementing the `Recordable` trait. diff --git a/log_db/tests/integration.rs b/log_db/tests/integration.rs index 37b4182..6ce3930 100644 --- a/log_db/tests/integration.rs +++ b/log_db/tests/integration.rs @@ -51,6 +51,12 @@ impl Recordable for Inst { (Field::Data, ValueType::bytes()), ] } + fn primary_key() -> Self::Field { + Field::Id + } + fn secondary_keys() -> Vec { + vec![Field::Name] + } fn into_record(self) -> Vec { vec![ @@ -89,7 +95,6 @@ fn test_initialize_only() { let data_dir = tmp_dir(); let _db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); } @@ -99,7 +104,6 @@ fn test_upsert_and_get_with_primary_memtable() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -122,7 +126,6 @@ fn test_upsert_and_get() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -175,7 +178,6 @@ fn test_get_nonexistant() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -189,6 +191,9 @@ impl Recordable for InstTestNullable { fn schema() -> Vec<(Self::Field, ValueType)> { vec![(Field::Id, ValueType::int())] } + fn primary_key() -> Self::Field { + Field::Id + } fn into_record(self) -> Vec { vec![Value::Null] @@ -204,7 +209,6 @@ fn test_upsert_fails_on_null_in_non_nullable_field() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -221,6 +225,9 @@ impl Recordable for InstTestNumValues { (Field::Name, ValueType::string()), ] } + fn primary_key() -> Self::Field { + Field::Id + } fn into_record(self) -> Vec { vec![Value::Int(0)] @@ -236,7 +243,6 @@ fn test_upsert_fails_on_invalid_number_of_values() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -250,6 +256,9 @@ impl Recordable for InstTestInvalidType { fn schema() -> Vec<(Self::Field, ValueType)> { vec![(Field::Id, ValueType::int())] } + fn primary_key() -> Self::Field { + Field::Id + } fn into_record(self) -> Vec { vec![Value::String("foo".to_string())] @@ -265,7 +274,6 @@ fn test_upsert_fails_on_invalid_value_type() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -278,8 +286,6 @@ fn test_upsert_and_find_all() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) - .secondary_keys(&[Field::Name]) .initialize() .expect("Failed to initialize DB instance"); @@ -322,6 +328,9 @@ impl Recordable for InstSingleId { fn schema() -> Vec<(Self::Field, ValueType)> { vec![(Field::Id, ValueType::int())] } + fn primary_key() -> Self::Field { + Field::Id + } fn into_record(self) -> Vec { vec![Value::Int(self.id)] @@ -350,7 +359,6 @@ fn test_multiple_writing_threads() { threads.push(thread::spawn(move || { let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -366,7 +374,6 @@ fn test_multiple_writing_threads() { // Read the records let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -394,7 +401,6 @@ fn test_one_writer_and_multiple_reading_threads() { let mut db = DB::::configure() .data_dir(&data_dir) .segment_size(1000) // should cause rotations - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -420,7 +426,6 @@ fn test_one_writer_and_multiple_reading_threads() { threads.push(thread::spawn(move || { let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -452,7 +457,6 @@ fn test_log_is_rotated_when_capacity_reached() { let mut db = DB::::configure() .data_dir(&data_dir) .segment_size(10 * record_len) // small log segment size - .primary_key(Field::Id) .initialize() .expect("Failed to initialize DB instance"); @@ -482,8 +486,6 @@ fn test_delete() { let data_dir = tmp_dir(); let mut db = DB::::configure() .data_dir(&data_dir) - .primary_key(Field::Id) - .secondary_keys(&[Field::Name]) .initialize() .expect("Failed to initialize DB instance"); -- cgit v1.3