aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md72
-rw-r--r--log_db/benches/benchmark.rs10
-rw-r--r--log_db/benches/utils.rs8
-rw-r--r--log_db/src/common.rs87
-rw-r--r--log_db/src/config.rs14
-rw-r--r--log_db/src/engine.rs86
-rw-r--r--log_db/src/lib.rs14
-rw-r--r--log_db/src/record.rs64
-rw-r--r--log_db/tests/integration.rs216
-rw-r--r--py_bindings/src/lib.rs64
10 files changed, 114 insertions, 521 deletions
diff --git a/README.md b/README.md
index c068b36..34e8d7c 100644
--- a/README.md
+++ b/README.md
@@ -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>()?;