diff options
| -rw-r--r-- | README.md | 72 | ||||
| -rw-r--r-- | log_db/benches/benchmark.rs | 10 | ||||
| -rw-r--r-- | log_db/benches/utils.rs | 8 | ||||
| -rw-r--r-- | log_db/src/common.rs | 87 | ||||
| -rw-r--r-- | log_db/src/config.rs | 14 | ||||
| -rw-r--r-- | log_db/src/engine.rs | 86 | ||||
| -rw-r--r-- | log_db/src/lib.rs | 14 | ||||
| -rw-r--r-- | log_db/src/record.rs | 64 | ||||
| -rw-r--r-- | log_db/tests/integration.rs | 216 | ||||
| -rw-r--r-- | py_bindings/src/lib.rs | 64 |
10 files changed, 114 insertions, 521 deletions
@@ -17,7 +17,7 @@ LogDB does not support: - Authentication or authorization in any capacity - Multiple tables -- Schema evolution, other than adding new nullable fields +- Type checking or schema evolution. These are outsourced to the application layer. See the [ARCHITECTURE.md](ARCHITECTURE.md) document for more details on the design and implementation of LogDB. @@ -58,30 +58,7 @@ struct Inst { pub name: Option<String>, } -// Implement the `Recordable` trait for your data type -impl Recordable for Inst { - // Use the `Field` enum - type Field = Field; - - // Define the schema as a vector of field names and corresponding types - fn schema() -> Vec<(Self::Field, Type)> { - vec![ - (Field::Id, Type::int()), - (Field::Name, Type::string().nullable()), - ] - } - - // Select the primary key field - fn primary_key() -> Self::Field { - Field::Id - } - - // Select the secondary key fields. All queries must be - // based on the primary key or secondary keys. - fn secondary_keys() -> Vec<Self::Field> { - vec![Field::Name] - } - +impl Inst { // Describe how to convert the data type to a vector of `Value`s fn into_record(self) -> Vec<Value> { vec![ @@ -96,8 +73,7 @@ impl Recordable for Inst { // Similarly, describe how to convert a vector of database values to the data type fn from_record(record: Vec<Value>) -> Self { let mut it = record.into_iter(); - - Inst { + let inst = Inst { id: match it.next().unwrap() { Value::Int(id) => id, other => panic!("Invalid value type: {:?}", other), @@ -107,14 +83,34 @@ impl Recordable for Inst { Value::Null => None, other => panic!("Invalid value type: {:?}", other), }, - } + }; + + assert_eq!(it.next(), None); + inst } } -fn main() { +fn example() -> DBResult<()> { // Initialize the database - let mut db = DB::<Inst>::configure() + let mut db = DB::configure() + // Set the directory where the database files are stored. .data_dir("data") + + // Select the database fields, i.e. columns. + .fields(vec![Field::Id, Field::Name]) + + // Select the primary key field. + .primary_key(Field::Id) + + // Select the secondary key fields. All queries must be + // based on the primary key or secondary keys. + .secondary_keys(vec![Field::Name]) + + // Define the conversion functions between the data type and database values. + .from_record(Inst::from_record) + .into_record(Inst::into_record) + + // Finish the builder pattern and initialize the database. .initialize()?; // Insert or update the record based on the primary key @@ -124,7 +120,7 @@ fn main() { })?; // Get the record by primary key - let found = db.get(Value::Int(1))?; + let found = db.get(&Value::Int(1))?; ... } @@ -160,14 +156,16 @@ maturin build --release # for the release version Then you can use the Python bindings like so: ```python -from log_db_py import DB, Value, ValueType, Record +from log_db_py import DB, Value, Record -config = DB.configure(); -config.primary_key = "id" -config.fields = [("id", ValueType.int().nullable())] +config = DB.configure() \ + .data_dir("data") \ + .fields(["id", "name"]) \ + .primary_key("id") \ + .secondary_keys(["name"]) \ + .initialize() -db = config.initialize() -db.upsert(Record(Value.int(10))) +db.upsert([Value.int(10)]) ``` ## Copyright and license diff --git a/log_db/benches/benchmark.rs b/log_db/benches/benchmark.rs index 4c9ad47..aa4b1bb 100644 --- a/log_db/benches/benchmark.rs +++ b/log_db/benches/benchmark.rs @@ -15,7 +15,7 @@ pub fn upsert_compacted(c: &mut Criterion) { .to_str() .expect("Failed to convert tmpdir path to str"); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::fields()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -47,7 +47,7 @@ pub fn delete_existing_compacted(c: &mut Criterion) { .to_str() .expect("Failed to convert tmpdir path to str"); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::fields()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -86,7 +86,7 @@ pub fn upsert_write_durability(c: &mut Criterion) { .to_str() .expect("Failed to convert tmpdir path to str"); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::fields()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -114,7 +114,7 @@ pub fn get_existing_compacted(c: &mut Criterion) { .to_str() .expect("Failed to convert tmpdir path to str"); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::fields()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -150,7 +150,7 @@ pub fn find_by_existing_compacted(c: &mut Criterion) { .to_str() .expect("Failed to convert tmpdir path to str"); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::fields()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) diff --git a/log_db/benches/utils.rs b/log_db/benches/utils.rs index 9f29cc6..3a949ae 100644 --- a/log_db/benches/utils.rs +++ b/log_db/benches/utils.rs @@ -18,12 +18,8 @@ pub struct Inst { } impl Inst { - pub fn schema() -> Vec<(Field, Type)> { - vec![ - (Field::Id, Type::int()), - (Field::Name, Type::string()), - (Field::Data, Type::bytes()), - ] + pub fn fields() -> Vec<Field> { + vec![Field::Id, Field::Name, Field::Data] } pub fn primary_key() -> Field { Field::Id diff --git a/log_db/src/common.rs b/log_db/src/common.rs index a450c28..2b40f52 100644 --- a/log_db/src/common.rs +++ b/log_db/src/common.rs @@ -183,58 +183,6 @@ pub enum IndexableValue { String(String), } -/// A primitive type -#[derive(Debug, Clone)] -pub enum PrimitiveType { - Int, - Decimal, - String, - Bytes, -} - -/// A primitive type + a nullability bit -#[derive(Debug, Clone)] -pub struct Type { - pub primitive: PrimitiveType, - pub nullable: bool, -} - -impl Type { - pub fn int() -> Self { - Type { - primitive: PrimitiveType::Int, - nullable: false, - } - } - - pub fn decimal() -> Self { - Type { - primitive: PrimitiveType::Decimal, - nullable: false, - } - } - - pub fn string() -> Self { - Type { - primitive: PrimitiveType::String, - nullable: false, - } - } - - pub fn bytes() -> Self { - Type { - primitive: PrimitiveType::Bytes, - nullable: false, - } - } - - pub fn nullable(&mut self) -> Self { - let mut new = self.clone(); - new.nullable = true; - new - } -} - #[derive(Debug, Clone)] pub enum Value { Null, @@ -341,41 +289,6 @@ impl Value { } } -pub fn type_check(value: &Value, value_type: &Type) -> bool { - match (value, value_type) { - ( - Value::Int(_), - Type { - primitive: PrimitiveType::Int, - .. - }, - ) => true, - ( - Value::Decimal(_), - Type { - primitive: PrimitiveType::Decimal, - .. - }, - ) => true, - ( - Value::Bytes(_), - Type { - primitive: PrimitiveType::Bytes, - .. - }, - ) => true, - ( - Value::String(_), - Type { - primitive: PrimitiveType::String, - .. - }, - ) => true, - (Value::Null, Type { nullable: true, .. }) => true, - _ => false, - } -} - pub fn get_secondary_memtable_index_by_field<Field: Eq>( sks: &Vec<Field>, field: &Field, diff --git a/log_db/src/config.rs b/log_db/src/config.rs index 2ed11c9..5b3cf26 100644 --- a/log_db/src/config.rs +++ b/log_db/src/config.rs @@ -1,7 +1,7 @@ use super::*; pub struct Schema<F> { - pub fields: Vec<(F, Type)>, + pub fields: Vec<F>, pub primary_key: F, pub secondary_keys: Vec<F>, } @@ -12,7 +12,7 @@ pub struct ConfigBuilder<T, F> { write_durability: Option<WriteDurability>, read_consistency: Option<ReadConsistency>, - schema: Option<Vec<(F, Type)>>, + fields: Option<Vec<F>>, primary_key: Option<F>, secondary_keys: Option<Vec<F>>, from_record: Option<fn(Vec<Value>) -> T>, @@ -29,7 +29,7 @@ impl<T, F: Eq + Clone> ConfigBuilder<T, F> { write_durability: None, read_consistency: None, - schema: None, + fields: None, primary_key: None, secondary_keys: None, from_record: None, @@ -71,8 +71,8 @@ impl<T, F: Eq + Clone> ConfigBuilder<T, F> { self } - pub fn schema(mut self, schema: Vec<(F, Type)>) -> Self { - self.schema = Some(schema); + pub fn fields(mut self, schema: Vec<F>) -> Self { + self.fields = Some(schema); self } @@ -98,7 +98,7 @@ impl<T, F: Eq + Clone> ConfigBuilder<T, F> { pub fn initialize(self) -> DBResult<DB<T, F>> { let schema = self - .schema + .fields .ok_or_else(|| DBError::ValidationError("Schema not set".to_string()))?; let primary_key = self .primary_key @@ -135,7 +135,7 @@ impl<T, F: Eq + Clone> ConfigBuilder<T, F> { #[derive(Clone)] pub struct Config<T, F> { - pub schema: Vec<(F, Type)>, + pub schema: Vec<F>, pub primary_key: F, pub secondary_keys: Vec<F>, pub from_record: fn(Vec<Value>) -> T, diff --git a/log_db/src/engine.rs b/log_db/src/engine.rs index 62a88cb..d593053 100644 --- a/log_db/src/engine.rs +++ b/log_db/src/engine.rs @@ -68,7 +68,7 @@ impl<T, F: Eq + Clone> Engine<T, F> { let primary_key_index = config .schema .iter() - .position(|(field, _)| field == &config.primary_key) + .position(|field| field == &config.primary_key) .ok_or(DBError::ValidationError( "Primary key not found in schema after initialize".to_owned(), ))?; @@ -80,14 +80,9 @@ impl<T, F: Eq + Clone> Engine<T, F> { // If any of the keys is not in the schema or // is not an IndexableValue, return an error for &key in &all_keys { - let (_, value_type) = config.schema.iter().find(|(field, _)| field == key).ok_or( + let _ = config.schema.iter().find(|&field| field == key).ok_or( DBError::ValidationError("Key must be present in the field schema".to_owned()), )?; - - match value_type.primitive { - PrimitiveType::Int | PrimitiveType::String => {} - _ => return Err(DBError::ValidationError("Key must be indexable".to_owned())), - } } let primary_memtable = PrimaryMemtable::new(); let secondary_memtables = config @@ -163,9 +158,6 @@ impl<T, F: Eq + Clone> Engine<T, F> { for ForwardLogReaderItem { record, index } in ForwardLogReader::new_with_index(metadata_file, data_file, from_index) { - // Validate that the values in the record are compatible with the schema - record.validate(&self.config.schema)?; - let log_key = LogKey::new(segnum, index); if record.tombstone { @@ -200,7 +192,7 @@ impl<T, F: Eq + Clone> Engine<T, F> { .config .schema .iter() - .position(|(f, _)| sk_field == f) + .position(|f| sk_field == f) .unwrap(); let sk = record.at(sk_field_index).as_indexable().unwrap(); @@ -221,7 +213,7 @@ impl<T, F: Eq + Clone> Engine<T, F> { .config .schema .iter() - .position(|(f, _)| sk_field == f) + .position(|f| sk_field == f) .unwrap(); let sk = record.at(sk_field_index).as_indexable().unwrap(); @@ -258,22 +250,11 @@ impl<T, F: Eq + Clone> Engine<T, F> { field: &F, values: impl Iterator<Item = &'a Value>, ) -> DBResult<Vec<(usize, Record)>> { - let field_type = self.get_field_type(field).ok_or(DBError::ValidationError( - "Field not found in schema".to_owned(), - ))?; - let indexables = values .map(|value| { - if type_check(&value, &field_type) { - value.as_indexable().ok_or(DBError::ValidationError( - "Queried value must be indexable".to_owned(), - )) - } else { - Err(DBError::ValidationError(format!( - "Queried value {:?} does not match key type: {:?}", - value, field_type - ))) - } + value.as_indexable().ok_or(DBError::ValidationError( + "Queried value must be indexable".to_owned(), + )) }) .collect::<DBResult<Vec<IndexableValue>>>()?; @@ -396,35 +377,26 @@ impl<T, F: Eq + Clone> Engine<T, F> { field: &F, range: B, ) -> DBResult<Vec<Record>> { - fn range_bound_to_indexable( - bound: Bound<&Value>, - field_type: &Type, - ) -> DBResult<Bound<IndexableValue>> { - fn convert(value: &Value, field_type: &Type) -> DBResult<IndexableValue> { - if !type_check(&value, field_type) { - return Err(DBError::ValidationError(format!( - "Queried value does not match type: {:?}", - field_type - ))); - } - value.as_indexable().ok_or(DBError::ValidationError( - "Queried value must be indexable".to_owned(), - )) - } - + fn range_bound_to_indexable(bound: Bound<&Value>) -> DBResult<Bound<IndexableValue>> { match bound { - Bound::Included(value) => convert(value, field_type).map(Bound::Included), - Bound::Excluded(value) => convert(value, field_type).map(Bound::Excluded), + Bound::Included(value) => value + .as_indexable() + .ok_or(DBError::ValidationError( + "Queried value must be indexable".to_owned(), + )) + .map(Bound::Included), + Bound::Excluded(value) => value + .as_indexable() + .ok_or(DBError::ValidationError( + "Queried value must be indexable".to_owned(), + )) + .map(Bound::Excluded), Bound::Unbounded => Ok(Bound::Unbounded), } } - let field_type = self.get_field_type(field).ok_or(DBError::ValidationError( - "Field not found in schema".to_owned(), - ))?; - - let start_indexable = range_bound_to_indexable(range.start_bound(), field_type)?; - let end_indexable = range_bound_to_indexable(range.end_bound(), field_type)?; + let start_indexable = range_bound_to_indexable(range.start_bound())?; + let end_indexable = range_bound_to_indexable(range.end_bound())?; let indexable_bounds = OwnedBounds::new(start_indexable, end_indexable); @@ -706,15 +678,6 @@ impl<T, F: Eq + Clone> Engine<T, F> { } #[inline] - fn get_field_type(&self, field: &F) -> Option<&Type> { - self.config - .schema - .iter() - .find(|(f, _)| f == field) - .map(|(_, t)| t) - } - - #[inline] pub fn with_exclusive_lock<A>( &mut self, f: impl FnOnce(&mut Self) -> DBResult<A>, @@ -801,10 +764,7 @@ mod tests { let mut db = DB::configure() .data_dir(data_dir.to_str().unwrap()) - .schema(vec![ - (Field::Id, Type::int()), - (Field::Name, Type::string()), - ]) + .fields(vec![Field::Id, Field::Name]) .primary_key(Field::Id) .secondary_keys(vec![Field::Name]) .from_record(TestInst2::from_record) diff --git a/log_db/src/lib.rs b/log_db/src/lib.rs index e92b68d..05db3dc 100644 --- a/log_db/src/lib.rs +++ b/log_db/src/lib.rs @@ -25,7 +25,7 @@ mod memtable_primary; mod memtable_secondary; mod record; -pub use common::{DBError, DBResult, OwnedBounds, Type, Value}; +pub use common::{DBError, DBResult, OwnedBounds, Value}; pub use config::{ReadConsistency, Schema, WriteDurability}; use common::*; @@ -58,9 +58,6 @@ impl<T, F: Eq + Clone> DB<T, F> { let record = Record::from(&(self.engine.config.into_record)(recordable)); debug!("Upserting record: {:?}", record); - record.validate(&self.engine.config.schema)?; - debug!("Record is valid"); - self.engine .with_exclusive_lock(move |engine| engine.upsert_record(record))?; @@ -297,7 +294,7 @@ mod tests { let mut db = DB::configure() .data_dir(data_dir.to_str().unwrap()) - .schema(vec![(Field::Id, Type::int())]) + .fields(vec![Field::Id]) .primary_key(Field::Id) .from_record(TestInst1::from_record) .into_record(TestInst1::into_record) @@ -384,7 +381,7 @@ mod tests { let mut db = DB::configure() .data_dir(data_dir.to_str().unwrap()) - .schema(vec![(Field::Id, Type::int())]) + .fields(vec![Field::Id]) .primary_key(Field::Id) .from_record(TestInst1::from_record) .into_record(TestInst1::into_record) @@ -438,10 +435,7 @@ mod tests { let mut db = DB::configure() .data_dir(data_dir.to_str().unwrap()) - .schema(vec![ - (Field::Id, Type::int()), - (Field::Name, Type::string()), - ]) + .fields(vec![Field::Id, Field::Name]) .primary_key(Field::Id) .secondary_keys(vec![Field::Name]) .from_record(TestInst2::from_record) diff --git a/log_db/src/record.rs b/log_db/src/record.rs index c373897..8108fb5 100644 --- a/log_db/src/record.rs +++ b/log_db/src/record.rs @@ -48,70 +48,6 @@ impl Record { pub fn at(&self, index: usize) -> &Value { &self.values[index] } - - pub fn validate<Field: Eq>(&self, schema: &[(Field, Type)]) -> DBResult<()> { - // If there are more values than schema fields, it's an error. - if self.values.len() > schema.len() { - return Err(DBError::ValidationError(format!( - "Record has more fields ({}) than expected by the schema ({})", - self.values.len(), - schema.len() - ))); - } - - for (i, (_, typ)) in schema.iter().enumerate() { - match self.values.get(i) { - Some(value) => { - if !Self::value_matches_type(value, typ) { - return Err(DBError::ValidationError(format!( - "Record field {} has incorrect type: {:?}, expected {:?}", - i, value, typ.primitive - ))); - } - } - // If the field is missing from the record... - None => { - // ...it's allowed only if the schema says the field is nullable. - if !typ.nullable { - return Err(DBError::ValidationError(format!( - "Record is missing field expected by the schema ({:?}) at index {}", - typ, i - ))); - } - } - } - } - - Ok(()) - } - - fn value_matches_type(value: &Value, typ: &Type) -> bool { - match (value, typ) { - (Value::Null, Type { nullable: true, .. }) => true, - ( - Value::Int(_), - Type { - primitive: PrimitiveType::Int, - .. - }, - ) => true, - ( - Value::String(_), - Type { - primitive: PrimitiveType::String, - .. - }, - ) => true, - ( - Value::Bytes(_), - Type { - primitive: PrimitiveType::Bytes, - .. - }, - ) => true, - _ => false, - } - } } #[cfg(test)] diff --git a/log_db/tests/integration.rs b/log_db/tests/integration.rs index b09bff3..c42f91c 100644 --- a/log_db/tests/integration.rs +++ b/log_db/tests/integration.rs @@ -44,12 +44,8 @@ struct Inst { } impl Inst { - fn schema() -> Vec<(Field, Type)> { - vec![ - (Field::Id, Type::int()), - (Field::Name, Type::string().nullable()), - (Field::Data, Type::bytes()), - ] + fn schema() -> Vec<Field> { + vec![Field::Id, Field::Name, Field::Data] } fn primary_key() -> Field { Field::Id @@ -94,7 +90,7 @@ impl Inst { fn test_initialize_only() { let data_dir = tmp_dir(); let _db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -108,7 +104,7 @@ fn test_initialize_only() { fn test_upsert_and_get_with_primary_memtable() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -135,7 +131,7 @@ fn test_upsert_and_get_with_primary_memtable() { fn test_upsert_and_get() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -192,7 +188,7 @@ fn test_upsert_and_get() { fn test_get_nonexistant() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -205,125 +201,11 @@ fn test_get_nonexistant() { assert!(result.is_none()); } -struct InstTestNullable {} -impl InstTestNullable { - fn schema() -> Vec<(Field, Type)> { - vec![(Field::Id, Type::int())] - } - fn primary_key() -> Field { - Field::Id - } - fn secondary_keys() -> Vec<Field> { - vec![] - } - - fn into_record(self) -> Vec<Value> { - vec![Value::Null] - } - - fn from_record(_record: Vec<Value>) -> Self { - Self {} - } -} - -#[test] -fn test_upsert_fails_on_null_in_non_nullable_field() { - let data_dir = tmp_dir(); - let mut db = DB::configure() - .schema(InstTestNullable::schema()) - .primary_key(InstTestNullable::primary_key()) - .secondary_keys(InstTestNullable::secondary_keys()) - .from_record(InstTestNullable::from_record) - .into_record(InstTestNullable::into_record) - .data_dir(&data_dir) - .initialize() - .expect("Failed to initialize DB instance"); - - // Null value - assert!(db.upsert(InstTestNullable {}).is_err()); -} - -struct InstTestNumValues {} -impl InstTestNumValues { - fn schema() -> Vec<(Field, Type)> { - vec![(Field::Id, Type::int()), (Field::Name, Type::string())] - } - fn primary_key() -> Field { - Field::Id - } - fn secondary_keys() -> Vec<Field> { - vec![] - } - - fn into_record(self) -> Vec<Value> { - vec![Value::Int(0)] - } - - fn from_record(_record: Vec<Value>) -> Self { - Self {} - } -} - -#[test] -fn test_upsert_fails_on_invalid_number_of_values() { - let data_dir = tmp_dir(); - let mut db = DB::configure() - .schema(InstTestNumValues::schema()) - .primary_key(InstTestNumValues::primary_key()) - .secondary_keys(InstTestNumValues::secondary_keys()) - .from_record(InstTestNumValues::from_record) - .into_record(InstTestNumValues::into_record) - .data_dir(&data_dir) - .initialize() - .expect("Failed to initialize DB instance"); - - // Missing values - assert!(db.upsert(InstTestNumValues {}).is_err()); -} - -struct InstTestInvalidType {} -impl InstTestInvalidType { - fn schema() -> Vec<(Field, Type)> { - vec![(Field::Id, Type::int())] - } - fn primary_key() -> Field { - Field::Id - } - fn secondary_keys() -> Vec<Field> { - vec![] - } - - fn into_record(self) -> Vec<Value> { - vec![Value::String("foo".to_string())] - } - - fn from_record(_record: Vec<Value>) -> Self { - Self {} - } -} - -#[test] -fn test_upsert_fails_on_invalid_value_type() { - let data_dir = tmp_dir(); - let mut db = DB::configure() - .schema(InstTestInvalidType::schema()) - .primary_key(InstTestInvalidType::primary_key()) - .secondary_keys(InstTestInvalidType::secondary_keys()) - .from_record(InstTestInvalidType::from_record) - .into_record(InstTestInvalidType::into_record) - .data_dir(&data_dir) - .initialize() - .expect("Failed to initialize DB instance"); - - // Invalid type - assert!(db.upsert(InstTestInvalidType {}).is_err()); -} - #[test] fn test_upsert_and_find_by() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -367,8 +249,8 @@ struct InstSingleId { } impl InstSingleId { - fn schema() -> Vec<(Field, Type)> { - vec![(Field::Id, Type::int())] + fn schema() -> Vec<Field> { + vec![Field::Id] } fn primary_key() -> Field { Field::Id @@ -403,7 +285,7 @@ fn test_multiple_writing_threads() { let data_dir = data_dir.clone(); threads.push(thread::spawn(move || { let mut db = DB::configure() - .schema(InstSingleId::schema()) + .fields(InstSingleId::schema()) .primary_key(InstSingleId::primary_key()) .secondary_keys(InstSingleId::secondary_keys()) .from_record(InstSingleId::from_record) @@ -423,7 +305,7 @@ fn test_multiple_writing_threads() { // Read the records let mut db = DB::configure() - .schema(InstSingleId::schema()) + .fields(InstSingleId::schema()) .primary_key(InstSingleId::primary_key()) .secondary_keys(InstSingleId::secondary_keys()) .from_record(InstSingleId::from_record) @@ -454,7 +336,7 @@ fn test_one_writer_and_multiple_reading_threads() { let data_dir = data_dir.clone(); threads.push(thread::spawn(move || { let mut db = DB::configure() - .schema(InstSingleId::schema()) + .fields(InstSingleId::schema()) .primary_key(InstSingleId::primary_key()) .secondary_keys(InstSingleId::secondary_keys()) .from_record(InstSingleId::from_record) @@ -485,7 +367,7 @@ fn test_one_writer_and_multiple_reading_threads() { // Add a writer that inserts the records threads.push(thread::spawn(move || { let mut db = DB::configure() - .schema(InstSingleId::schema()) + .fields(InstSingleId::schema()) .primary_key(InstSingleId::primary_key()) .secondary_keys(InstSingleId::secondary_keys()) .from_record(InstSingleId::from_record) @@ -520,7 +402,7 @@ fn test_log_is_rotated_when_capacity_reached() { + (1 + 8 + 3); // bytes tag + bytes length + bytes data let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -555,7 +437,7 @@ fn test_log_is_rotated_when_capacity_reached() { fn test_delete() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -597,7 +479,7 @@ fn test_delete() { fn test_delete_by() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -654,7 +536,7 @@ fn test_delete_by() { fn test_range_by_id() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -707,7 +589,7 @@ fn test_range_by_id() { fn test_batch_find_by() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -745,7 +627,7 @@ fn test_batch_find_by() { fn test_commit_transaction() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -783,7 +665,7 @@ fn test_commit_transaction() { fn test_rollback_transaction() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -833,15 +715,12 @@ struct InstWithNewNullableField { } impl InstWithNewNullableField { - fn schema() -> Vec<(FieldWithNewNullableField, Type)> { + fn schema() -> Vec<FieldWithNewNullableField> { vec![ - (FieldWithNewNullableField::Id, Type::int()), - (FieldWithNewNullableField::Name, Type::string().nullable()), - (FieldWithNewNullableField::Data, Type::bytes()), - ( - FieldWithNewNullableField::MaybeStr, - Type::string().nullable(), - ), + FieldWithNewNullableField::Id, + FieldWithNewNullableField::Name, + FieldWithNewNullableField::Data, + FieldWithNewNullableField::MaybeStr, ] } @@ -902,7 +781,7 @@ fn test_add_nullable_field() { // Insert a record with 3 fields { let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) @@ -921,7 +800,7 @@ fn test_add_nullable_field() { // Insert a record with 4 fields (last is nullable) let mut db = DB::configure() - .schema(InstWithNewNullableField::schema()) + .fields(InstWithNewNullableField::schema()) .primary_key(InstWithNewNullableField::primary_key()) .secondary_keys(InstWithNewNullableField::secondary_keys()) .from_record(InstWithNewNullableField::from_record) @@ -949,50 +828,11 @@ fn test_add_nullable_field() { } #[test] -fn test_add_non_nullable_field() { - let data_dir = tmp_dir(); - - // Insert a record with just one field - { - let mut db = DB::configure() - .schema(InstSingleId::schema()) - .primary_key(InstSingleId::primary_key()) - .secondary_keys(InstSingleId::secondary_keys()) - .from_record(InstSingleId::from_record) - .into_record(InstSingleId::into_record) - .data_dir(&data_dir) - .initialize() - .expect("Failed to initialize DB instance"); - - db.upsert(InstSingleId { id: 0 }).unwrap(); - } - - // Configure the DB with three fields, one of which is non-nullable - // This should fail - match DB::configure() - .schema(Inst::schema()) - .primary_key(Inst::primary_key()) - .secondary_keys(Inst::secondary_keys()) - .from_record(Inst::from_record) - .into_record(Inst::into_record) - .data_dir(&data_dir) - .initialize() - { - Ok(_) => panic!("Expected initialization to fail"), - Err(DBError::ValidationError(e)) => { - // Expected - error!("Expected: {:?}", e); - } - Err(e) => panic!("Unexpected error: {:?}", e), - } -} - -#[test] fn test_delete_by_multiple_indexes() { let data_dir = tmp_dir(); let mut db = DB::configure() - .schema(Inst::schema()) + .fields(Inst::schema()) .primary_key(Inst::primary_key()) .secondary_keys(Inst::secondary_keys()) .from_record(Inst::from_record) diff --git a/py_bindings/src/lib.rs b/py_bindings/src/lib.rs index 7749839..53544e2 100644 --- a/py_bindings/src/lib.rs +++ b/py_bindings/src/lib.rs @@ -9,49 +9,6 @@ use std::ops::Bound as StdBound; type PyRecord = Vec<Value>; type PyField = String; -#[pyclass] -#[derive(Clone)] -struct Type { - typ: log_db::Type, -} - -#[pymethods] -impl Type { - #[staticmethod] - fn int() -> Self { - Type { - typ: log_db::Type::int(), - } - } - - #[staticmethod] - fn decimal() -> Self { - Type { - typ: log_db::Type::decimal(), - } - } - - #[staticmethod] - fn string() -> Self { - Type { - typ: log_db::Type::string(), - } - } - - #[staticmethod] - fn bytes() -> Self { - Type { - typ: log_db::Type::bytes(), - } - } - - fn nullable(&self) -> Self { - Type { - typ: self.typ.clone().nullable(), - } - } -} - pub const WRITE_DURABILITY_FLUSH: u8 = 0; pub const WRITE_DURABILITY_FLUSH_SYNC: u8 = 1; @@ -64,7 +21,7 @@ struct Config { segment_size: Option<usize>, write_durability: Option<log_db::WriteDurability>, read_consistency: Option<log_db::ReadConsistency>, - schema: Option<Vec<(PyField, Type)>>, + fields: Option<Vec<PyField>>, primary_key: Option<PyField>, secondary_keys: Option<Vec<PyField>>, } @@ -121,11 +78,11 @@ impl Config { Ok(slf) } - pub fn schema<'a>( + pub fn fields<'a>( mut slf: PyRefMut<'a, Self>, - schema: Vec<(PyField, Type)>, + fields: Vec<PyField>, ) -> PyResult<PyRefMut<'a, Self>> { - slf.schema = Some(schema); + slf.fields = Some(fields); Ok(slf) } @@ -161,15 +118,15 @@ impl Config { let tmp = self.read_consistency.as_ref().unwrap(); config = config.read_consistency(tmp.clone()); } - if self.schema.is_some() { - let schema = self - .schema + if self.fields.is_some() { + let fields = self + .fields .as_ref() .unwrap() .iter() - .map(|(name, typ)| (name.clone(), typ.typ.clone())) + .map(|name| name.clone()) .collect(); - config = config.schema(schema); + config = config.fields(fields); } if self.primary_key.is_some() { config = config.primary_key(self.primary_key.as_ref().unwrap().to_string()); @@ -323,7 +280,7 @@ impl DB { segment_size: None, write_durability: None, read_consistency: None, - schema: None, + fields: None, primary_key: None, secondary_keys: None, } @@ -448,7 +405,6 @@ impl PyRangeBound { #[pymodule(name = "log_db")] fn log_db_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::<DB>()?; - m.add_class::<Type>()?; m.add_class::<Value>()?; m.add_class::<PyRangeBound>()?; |
