#memory-database #db #memory #bit-flags #bits #bit

venndb

an in-memory database in Rust for rows queried using bit (flag) columns

8 releases (5 breaking)

0.5.0 May 23, 2024
0.4.0 Apr 18, 2024
0.3.0 Apr 18, 2024
0.2.1 Apr 15, 2024
0.0.0 Dec 16, 2023

#152 in Database interfaces

Download history 9/week @ 2024-02-26 15/week @ 2024-04-01 363/week @ 2024-04-08 813/week @ 2024-04-15 293/week @ 2024-04-22 326/week @ 2024-04-29 326/week @ 2024-05-06 369/week @ 2024-05-13 323/week @ 2024-05-20 451/week @ 2024-05-27 166/week @ 2024-06-03 307/week @ 2024-06-10

1,310 downloads per month

MIT/Apache

3MB
441 lines

VennDB

An append-only in-memory database in Rust for rows queried using bit (flag) columns. This database is designed for a very specific use case where you have mostly static data that you typically load at startup and have to query constantly using very simple filters. Datasets like these can be large and should be both fast and compact.

For the limited usecases where venndb can be applied to, it has less dependencies and is faster then traditional choices, such as a naive implementation or a more heavy lifted dependency such as Sqlite.

See the benchmarks for more information on this topic.

This project was developed originally in function of rama, where you can see it being used for example to provide an in-memory (upstream) proxy database. Do let us know in case you use it as well in your project, such that we can assemble a showcase list.

venndb banner

Crates.io Docs.rs MIT License Apache 2.0 License rust version Build Status

Discord Buy Me A Coffee GitHub Sponsors

šŸ’¬ Come join us at Discord on the #venndb public channel. To ask questions, discuss ideas and ask how venndb may be useful for you.

Index

venndb manual:

  • Usage: quick introduction on how to use venndb;
  • Benchmarks: benchmark results to give you a rough idea how venndb peforms for the use case it is made for (write once, read constantly, using binary filters mostly);
  • Q&A: Frequently Asked Questions (FAQ);
  • Example: the full example (expanded version from Usage), tested and documented;
  • Generated Code Summary: a documented overview of the API that venndb will generate for you when using #[derive(VennDB)] on your named field struct;

technical information:

misc:

Usage

Add venndb as a dependency:

cargo add venndb

and import the derive macro in the module where you want to use it:

use venndb::VennDB

#[derive(Debug, VennDB)]
pub struct Employee {
    #[venndb(key)]
    id: u32,
    name: String,
    is_manager: Option<bool>,
    is_admin: bool,
    #[venndb(skip)]
    foo: bool,
    #[venndb(filter, any)]
    department: Department,
    #[venndb(filter)]
    country: Option<String>,
}

fn main() {
    let db = EmployeeDB::from_iter(/* .. */);

    let mut query = db.query();
    let employee = query
        .is_admin(true)
        .is_manager(false)
        .department(Department::Engineering)
        .execute()
        .expect("to have found at least one")
        .any();

    println!("non-manager admin engineer: {:?}", employee);
}

See the full example or the "Generated Code Summary" chapter below to learn how to use the VennDB and its generated code.

Benchmarks

Benchmarks displayed here are taken on a dev machine with following specs:

Macbook Pro ā€” 16 inch (2023)
Chip: Apple M2 Pro
Memory: 16 GB
OS: Sonoma 14.2

The benchmarks tests 3 different implementations of a proxy database

The benchmarks are created by:

  1. running just bench;
  2. copying the output into ./scripts/plot_bench_charts and running it.

Snippet that is ran for each 3 implementations:

fn test_db(db: &impl ProxyDB) {
    let i = next_round();

    let pool = POOLS[i % POOLS.len()];
    let country = COUNTRIES[i % COUNTRIES.len()];

    let result = db.get(i as u64);
    divan::black_box(result);

    let result = db.any_tcp(pool, country);
    divan::black_box(result);

    let result = db.any_socks5_isp(pool, country);
    divan::black_box(result);
}

Benchmark Performance Results

Performance for Database with 100 records:

Proxy DB Fastest (Āµs) Median (Āµs) Slowest (Āµs)
naive_proxy_db_100 6.50 8.00 18.04
sql_lite_proxy_db_100 32.58 37.37 302.00
venn_proxy_db_100 0.89 0.92 2.74

Performance for Database with 12_500 records:

Proxy DB Fastest (Āµs) Median (Āµs) Slowest (Āµs)
naive_proxy_db_12_500 404.00 407.70 478.70
sql_lite_proxy_db_12_500 1061.00 1073.00 1727.00
venn_proxy_db_12_500 16.04 16.97 25.54

Performance for Database with 100_000 records:

Proxy DB Fastest (Āµs) Median (Āµs) Slowest (Āµs)
naive_proxy_db_100_000 3790.00 3837.00 5731.00
sql_lite_proxy_db_100_000 8219.00 8298.00 9424.00
venn_proxy_db_100_000 124.20 129.20 156.30

We are not database nor hardware experts though. Please do open an issue if you think these benchmarks are incorrect or if related improvements can be made. Contributions in the form of Pull requests are welcomed as well.

See the Contribution guidelines for more information.

Benchmark Allocations Results

Allocations for Database with 100 records:

Proxy DB Fastest (KB) Median (KB) Slowest (KB)
naive_proxy_db_100 0.33 0.33 0.33
sql_lite_proxy_db_100 4.04 4.04 4.04
venn_proxy_db_100 0.05 0.05 0.05

Allocations for Database with 12_500 records:

Proxy DB Fastest (KB) Median (KB) Slowest (KB)
naive_proxy_db_12_500 40.73 40.73 40.73
sql_lite_proxy_db_12_500 5.03 5.02 5.03
venn_proxy_db_12_500 3.15 3.15 3.15

Allocations for Database with 100_000 records:

Proxy DB Fastest (KB) Median (KB) Slowest (KB)
naive_proxy_db_100_000 323.30 323.30 323.70
sql_lite_proxy_db_100_000 5.02 5.02 5.01
venn_proxy_db_100_000 25.02 25.02 25.02

We are not database nor hardware experts though. Please do open an issue if you think these benchmarks are incorrect or if related improvements can be made. Contributions in the form of Pull requests are welcomed as well.

See the Contribution guidelines for more information.

Q&A

ā“ Why use this over Database X?

venndb is not a database, but is close enough for some specific purposes. It shines for long-lived read-only use cases where you need to filter on plenty of binary properties and get a rando matching result.

Do not try to replace your usual database needs with it.

ā“ Where can I propose a new feature X or some other improvement?

Please open an issue and also read the Contribution guidelines. We look forward to hear from you.

Alternatively you can also join our Discord and start a conversation / discussion over there.

ā“ Can I use whatever type for a #[venndb(filter)] property?

Yes, as long as it implements PartialEq + Eq + Hash + Clone. That said, we do recommend that you use enum values if you can, or some other highly restricted form.

Using for example a String directly is a bad idea as that would mean that bE != Be != BE != Belgium != Belgique != Belgiƫ. Even though these are really referring all to the same country. In such cases a much better idea is to at the very least create a wrapper type such as struct Country(String), to allow you to enforce sanitization/validation when creating the value and ensuring the hashes will be the same for those values that are conceptually the same.

ā“ How do I make a filter optional?

Both filters (bool properties) and filter maps (T != bool properties with the #[venndb(filter)] attribute) can be made optional by wrapping the types with Option, resulting in Option<bool> and Option<T>.

Rows that have the Option::None value for such an optional column cannot filter on that property, but there is no other consequence beyond that.

ā“ Why can do keys have to be unique and non-optional?

Within venndb keys are meant to be able to look up, a row which was previously received via filters.

As such it makes no sense for such keys to be:

  • duplicate: it would mean: as that can result in multiple rows or the wrong row to be returned;
  • optional: as that would mean the row cannot be looked up when the key is not defined;

ā“ How can I allow some rows to match for any value of a certain (filter) column?

Filter maps can allow to have a value to match all other values. It is up to you to declare the filter as such, and to also define for that type what the one value to rule them all is.

Usage:

use venndb::{Any, VennDB};
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Department {
  Any,
  Hr,
  Engineering,
}

impl Any for Department {
  fn is_any(&self) -> bool {
    self == Department::Any
  }
}

#[derive(Debug, VennDB)]
pub struct Employee {
  name: String,
  #[venndb(filter, any)]
  department: Department,
}

let db = EmployeeDB::from_iter([
  Employee { name: "Jack".to_owned(), department: Department::Any },
  Employee { name: "Derby".to_owned(), department: Department::Hr },
]);
let mut query = db.query();

// will match Jack and Derby, as Jack is marked as Any, meaning it can work for w/e value
let hr_employees: Vec<_> = query.department(Department::Hr).execute().unwrap().iter().collect();
assert_eq!(hr_employees.len(), 2);

ā“ How can I provide custom validation of rows prior to them getting appended?

Is is possible to validate a row based on one or multiple of its properties? Validate in function of relationship between multiple properties? Is it possible to provide custom validation to prevent rows from getting appended that do not adhere to custom validation rules?

Yes to all of the above.

Example:

#[derive(Debug, VennDB)]
#[venndb(validator = my_validator_fn)]
pub struct Value {
   pub foo: String,
   pub bar: u32,
}

fn my_validator_fn(value: &Value) -> bool {
    !value.foo.is_empty() && value.bar > 0
}

let mut db = ValueDB::default();
assert!(db.append(Value {
    foo: "".to_owned(),
    bar: 42,
}).is_err()); // fails because foo == empty

ā“ Why do any filter values only match rows that have an any value for that property?

Let's say I have the following struct:

use venndb::{Any, VennDB};

#[derive(Debug, VennDB)]
pub struct Value {
   #[venndb(filter, any)]
   pub foo: MyString,
   pub bar: u32,
}

#[derive(Debug)]
pub struct MyString(String);

impl Any for MyString {
    fn is_any(&self) -> bool {
        self.0 == "*"
    }
}

let db = ValueDB::from_iter([
    Value {
        foo: MyString("foo".to_owned()),
        bar: 8,
    },
    Value {
        foo: MyString("*".to_owned()),
        bar: 16,
    }
].into_Iter()).unwrap();

let mut query = db.query();
query.foo(MyString("*".to_owned()));
let value = query.execute().unwrap().any();
// this will never match the row with bar == 8,
// tiven foo != an any value
assert_eq!(value.bar, 16);

Why is this true? Because it is correct.

Allowing it also to match the value foo would unfairly give more chances for foo to be selected over the any value. This might not seem like a big difference, but it is. Because what if we generate a random string for Values with an _any value? If we would allow all rows to be matched then that logic is now rigged, with a value of foo being more likely then other strings.

As such the only correct answer when filtering for any value, is to return rows that have any value.

ā“ How can I query on multiple variants of a "filter map" property?

Just call the query method multiple times. It will allow you to match rows that have either of these values.

Example

use venndb::{Any, VennDB};

#[derive(Debug, VennDB)]
pub struct Value {
   #[venndb(filter)]
   pub foo: String,
   pub bar: u32,
}

let db = ValueDB::from_iter([
    Value {
        foo: "a".to_owned(),
        bar: 8,
    },
    Value {
        foo: "b".to_owned(),
        bar: 12,
    },
    Value {
        foo: "c".to_owned(),
        bar: 16,
    },
].into_Iter()).unwrap();

let mut query = db.query();
query.foo(MyString("a".to_owned()));
query.foo(MyString("c".to_owned()));
let values: Vec<_> = query.execute().unwrap().iter().collect();
assert_eq!(values.len(), 2);
assert_eq!(values[0].bar, 8);
assert_eq!(values[0].bar, 16);

Example

Here follows an example demonstrating all the features of VennDB.

If you prefer a summary of what is generated, or do not understand something from the example below, you can also read the "Generated Code Summary" chapter below.

use itertools::Itertools;
use venndb::VennDB;

#[derive(Debug, VennDB)]
// These attributes are optional,
// e.g. by default the database would be called `EmployeeDB` (name + 'DB').
#[venndb(name = "EmployeeInMemDB", validator = employee_validator)]
pub struct Employee {
    // you can use the `key` arg to be able to get an `Employee` instance
    // directly by this key. It will effectively establishing a mapping from key to a reference
    // of that Employee in the database. As such keys have to have unique values,
    // or else you get an error while appending / creating the DB.
    //
    // NOTE: keys do not only have to be unique, they also have to implement `Clone`!!
    //
    // A property cannot be a filter and a key at the same time,
    // trying to do so will result in a compile-team failure.
    #[venndb(key)]
    id: u32,
    name: String,
    is_manager: bool,
    is_admin: bool,
    // filter (booleans) can be made optional,
    // meaning that the row will not be able to be filtered (found)
    // on this column when the row has a `None` value for it
    is_active: Option<bool>,
    // booleans are automatically turned into (query) filters,
    // use the `skip` arg to stop this. As such it is only really needed for
    // bool properties :)
    #[venndb(skip)]
    foo: bool,
    // non-bool values can also be turned into filters, turning them into 2D filters.
    // For each uniquely inserted Department variant that is inserted,
    // a new filter is kept track of. This allows you to apply a (query) filter
    // based on department, a pretty useful thing to be able to do.
    //
    // NOTE: this does mean that such filter-map types have to also be:
    // `PartialEq + Eq + Hash + Clone`!!
    //
    // A property cannot be a filter and a key at the same time,
    // trying to do so will result in a compile-team failure.
    #[venndb(filter)]
    department: Department,
    // similar to regular bool filters,
    // filter maps can also be optional.
    // When a filter map is optional and the row's property for that filter is None,
    // it will not be registered and thus not be able to filtered (found) on that property
    #[venndb(filter)]
    country: Option<String>,
}

fn employee_validator(employee: &Employee) -> bool {
    employee.id > 0
}

fn main() {
    let db = EmployeeInMemDB::from_iter([
        RawCsvRow("1,John Doe,true,false,true,false,Engineering,USA"),
        RawCsvRow("2,Jane Doe,false,true,true,true,Sales,"),
        RawCsvRow("3,John Smith,false,false,,false,Marketing,"),
        RawCsvRow("4,Jane Smith,true,true,false,true,HR,"),
        RawCsvRow("5,John Johnson,true,true,true,true,Engineering,"),
        RawCsvRow("6,Jane Johnson,false,false,,false,Sales,BE"),
        RawCsvRow("7,John Brown,true,false,true,false,Marketing,BE"),
        RawCsvRow("8,Jane Brown,false,true,true,true,HR,BR"),
    ])
    .expect("MemDB created without errors (e.g. no duplicate keys)");

    println!(">>> Printing all employees...");
    let all_employees: Vec<_> = db.iter().collect();
    assert_eq!(all_employees.len(), 8);
    println!("All employees: {:#?}", all_employees);

    println!(">>> You can lookup an employee by any registered key...");
    let employee = db
        .get_by_id(&2)
        .expect("to have found an employee with ID 2");
    assert_eq!(employee.name, "Jane Doe");

    println!(">>> Querying for all managers...");
    let mut query = db.query();
    query.is_manager(true);
    let managers: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(managers.len(), 4);
    assert_eq!(
        managers.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
        [1, 4, 5, 7]
    );

    println!(">>> Querying for all managers with a last name of 'Johnson'...");
    let managers_result = query
        .execute()
        .expect("to have found at least one")
        .filter(|e| e.name.ends_with("Johnson"))
        .expect("to have found a manager with a last name of Johnson");
    let managers = managers_result.iter().collect::<Vec<_>>();
    assert_eq!(managers.len(), 1);
    assert_eq!(managers.iter().map(|e| e.id).collect::<Vec<_>>(), [5]);

    println!(">>> You can also just get the first result if that is all you care about...");
    let manager = managers_result.first();
    assert_eq!(manager.id, 5);

    println!(">>> Querying for a random active manager in the Engineering department...");
    let manager = query
        .reset()
        .is_active(true)
        .is_manager(true)
        .department(Department::Engineering)
        .execute()
        .expect("to have found at least one")
        .any();
    assert!(manager.id == 1 || manager.id == 5);

    println!(">>> Optional bool filters have three possible values, where None != false. An important distinction to make...");
    let mut query = db.query();
    query.is_active(false);
    let inactive_employees: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(inactive_employees.len(), 1);
    assert_eq!(inactive_employees[0].id, 4);

    println!(">>> If you want you can also get the Employees back as a Vec, dropping the DB data all together...");
    let employees = db.into_rows();
    assert_eq!(employees.len(), 8);
    assert!(employees[1].foo);
    println!("All employees: {:?}", employees);

    println!(">>> You can also get the DB back from the Vec, if you want start to query again...");
    // of course better to just keep it as a DB to begin with, but let's pretend this is ok in this example
    let mut db = EmployeeInMemDB::from_rows(employees).expect("DB created without errors");
    assert_eq!(db.iter().count(), 8);

    println!(">>> Querying for all active employees in the Sales department...");
    let mut query = db.query();
    query.is_active(true);
    query.department(Department::Sales);
    let sales_employees: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(sales_employees.len(), 1);
    assert_eq!(sales_employees[0].name, "Jane Doe");

    println!(">>> Filter maps that are optional work as well, e.g. you can query for all employees from USA...");
    query.reset().country("USA".to_owned());
    let usa_employees: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(usa_employees.len(), 1);
    assert_eq!(usa_employees[0].id, 1);

    println!(">>> At any time you can also append new employees to the DB...");
    assert_eq!(EmployeeInMemDBErrorKind::DuplicateKey, db
        .append(RawCsvRow("8,John Doe,true,false,true,false,Engineering,"))
        .unwrap_err().kind());
    println!(">>> This will fail however if a property is not correct (e.g. ID (key) is not unique in this case), let's try this again...");
    assert!(db
        .append(RawCsvRow("9,John Doe,false,true,true,false,Engineering,"))
        .is_ok());
    assert_eq!(db.len(), 9);

    println!(">>> Rows are also validated prior to appending in case a validator is defined...");
    println!("    The next insertion will fail due to the id being zero, a condition defined in the custom validator...");
    assert_eq!(EmployeeInMemDBErrorKind::InvalidRow, db
        .append(RawCsvRow("0,John Doe,true,false,true,false,Engineering,"))
        .unwrap_err().kind());

    println!(">>> This new employee can now also be queried for...");
    let mut query = db.query();
    query.department(Department::Engineering).is_manager(false);
    let new_employee: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(new_employee.len(), 1);
    assert_eq!(new_employee[0].id, 9);

    println!(">>> You can also extend it using an IntoIterator...");
    db.extend([
        RawCsvRow("10,Glenn Doe,false,true,true,true,Engineering,"),
        RawCsvRow("11,Peter Miss,true,true,true,true,HR,USA"),
    ])
    .unwrap();
    let mut query = db.query();
    query
        .department(Department::HR)
        .is_manager(true)
        .is_active(true)
        .is_admin(true);
    let employees: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(employees.len(), 1);
    assert_eq!(employees[0].id, 11);

    println!(">>> There are now 2 employees from USA...");
    query.reset().country("USA".to_owned());
    let employees: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(employees.len(), 2);
    assert_eq!(
        employees.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
        [1, 11]
    );

    println!(">>> All previously data is still there as well of course...");
    query
        .reset()
        .is_active(true)
        .is_manager(true)
        .department(Department::Engineering);
    let managers: Vec<_> = query
        .execute()
        .expect("to have found at least one")
        .iter()
        .collect();
    assert_eq!(managers.len(), 2);
    assert_eq!(
        managers.iter().map(|e| e.id).sorted().collect::<Vec<_>>(),
        [1, 5]
    );
}

#[derive(Debug)]
struct RawCsvRow<S>(S);

impl<S> From<RawCsvRow<S>> for Employee
where
    S: AsRef<str>,
{
    fn from(RawCsvRow(s): RawCsvRow<S>) -> Employee {
        let mut parts = s.as_ref().split(',');
        let id = parts.next().unwrap().parse().unwrap();
        let name = parts.next().unwrap().to_string();
        let is_manager = parts.next().unwrap().parse().unwrap();
        let is_admin = parts.next().unwrap().parse().unwrap();
        let is_active = match parts.next().unwrap() {
            "" => None,
            s => Some(s.parse().unwrap()),
        };
        let foo = parts.next().unwrap().parse().unwrap();
        let department = parts.next().unwrap().parse().unwrap();
        let country = match parts.next().unwrap() {
            "" => None,
            s => Some(s.to_string()),
        };
        Employee {
            id,
            name,
            is_manager,
            is_admin,
            is_active,
            foo,
            department,
            country,
        }
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Department {
    Engineering,
    Sales,
    Marketing,
    HR,
}

impl std::str::FromStr for Department {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Engineering" => Ok(Department::Engineering),
            "Sales" => Ok(Department::Sales),
            "Marketing" => Ok(Department::Marketing),
            "HR" => Ok(Department::HR),
            _ => Err(()),
        }
    }
}

Generated Code Summary

In this chapter we'll list the API as generated by VennDB for the following example code from above:

#[derive(Debug, VennDB)]
#[venndb(name = "EmployeeInMemDB", validator = employee_validator)]
pub struct Employee {
    #[venndb(key)]
    id: u32,
    name: String,
    is_manager: bool,
    is_admin: bool,
    is_active: Option<bool>,
    #[venndb(skip)]
    foo: bool,
    #[venndb(filter)]
    department: Department,
    country: Option<String>,
}

The following public-API datastructures will be generated:

  • struct EmployeeInMemDB: the database, that can be used to query (by filters) or look up data (by keys);
  • enum EmployeeInMemDBError: the error type that is returned when mutating the DB and a property of the to be inserted row;
  • enum EmployeeInMemDBErrorKind: the kind of error that can happen as described for EmployeeInMemDBError;
  • struct EmployeeInMemDBQuery: the query builder that is used to build a query that can be executed to query data from the db using filters;
  • struct EmployeeInMemDBQueryResult: the result when querying using EmployeeInMemDBQuery and at least one row was found that matched the defined filters;
  • struct EmployeeInMemDBQueryResultIter: the iterator type that is used when calling EmployeeInMemDBQueryResult::iter. It has no methods/api other then the fact that it is an Iterator and can be used as one;

The visual specifiers of these datastructures will be the same as the struct that the VennDB macro is applied to. E.g. in this example Employee has a specifier of pub so the above datastructures and their public-apy methods will also be pub.

There are also some other helper datastructures generated ā€” all prefixed with the database name, e.g. EmployeeInMemDB in this example ā€” but we do not mention here as they should not be relied upon and given the prefix it should cause no conflict. In case you do not want to expose these structures to the outside you can wrap your struct within its own mod (module).

Generated Code Summary: Method API

Database: (e.g. EmployeeInMemDB):

fn signature description
EmployeeInMemDB::new() -> EmployeeInMemDB create a new database with zero capacity
EmployeeInMemDB::default() -> EmployeeInMemDB same as EmployeeInMemDB::new() -> EmployeeInMemDB
EmployeeInMemDB::capacity(capacity: usize) -> EmployeeInMemDB create a new database with the given capacity, but no rows already inserted
EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> EmployeeInMemDB or EmployeeInMemDB::from_rows(rows: ::std::vec::Vec<Employee>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>> constructor to create the database directly from a heap-allocated list of data instances. The second version is the one used if at least one #[venndb(key)] property is defined, otherwise it is the first one (without the Result).
EmployeeInMemDB::from_iter(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> EmployeeInMemDB or EmployeeInMemDB::from_rows(iter: impl ::std::iter::IntoIterator<Item = impl ::std::convert::Into<Employee>>) -> Result<EmployeeInMemDB, EmployeeInMemDBError<::std::vec::Vec<Employee>>> Same as from_rows but using an iterator instead. The items do not have to be an Employee but can be anything that can be turned into one. E.g. in our example above we defined a struct RawCsvRow that was turned on the fly into an Employee. This happens all at once prior to inserting the database, which is why the version with a result does return a Vec and not an iterator.
EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>) or EmployeeInMemDB::append(&mut self, data: impl ::std::convert::Into<Employee>) -> Result<(), EmployeeInMemDBError<Employee>> append a single row to the database. Depending on whether or not a #[venndb(key)] property is defined it will generate the Result version or not. Same as from_rows and from_iter
EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee> or EmployeeInMemDB::extend<I, Item>(&mut self, iter: I) -> Result<(), EmployeeInMemDBError<(Employee, I::IntoIter)>> where I: ::std::iter::IntoIterator<Item = Item>, Item: ::std::convert::Into<Employee> extend the database with the given iterator, once again returning a result in case such insertion can go wrong (e.g. because keys are used (duplication) or a row is invalid in case a validator is defined). Otherwise this function will return nothing.
EmployeeInMemDB::get_by_id<Q>(&self, data: impl ::std::convert::Into<Employee>) -> Option<&Employee> where Employee ::std::borrow::Borrow<Q>, Q: ::std::hash::Hash + ::std::cmp::Eq + ?::std::marker::Sized look up a row by the id key property. This method will be generated for each property marked with #[venndb(key). e.g. if you have key property named foo: MyType property there will be also a get_by_foo(&self, ...) method generated.
EmployeeInMemDB::query(&self) -> EmployeeInMemDBQuery create a EmployeeInMemDBQuery builder to compose a filter composition to query the database. The default builder will match all rows. See the method API for EmployeeInMemDBQuery for more information

Query (e.g. EmployeeInMemDBQuery)

fn signature description
EmployeeInMemDBQuery::reset(&mut self) -> &mut Self reset the query, bringing it back to the clean state it has on creation
EmployeeInMemDBQuery::execute(&self) -> Option<EmployeeInMemDBQueryResult<'a>> return the result of the query using the set filters. It will be None in case no rows matched the defined filters. Or put otherwise, the result will contain at least one row when Some(_) is returned.
EmployeeInMemDBQuery::is_manager(&mut self, value: bool) -> &mut Self a filter setter for a bool filter. One such method per bool filter (that isn't skipped) will be available. E.g. if you have foo filter then there will be a EmployeeInMemDBQuery:foo method. For bool filters that are optional (Option<bool>) this method is also generated just the same.
EmployeeInMemDBQuery::department(&mut self, value: impl ::std::convert::Into<Department>) -> &mut Self a filter (map) setter for a non-bool filter. One such method per non-bool filter will be available. You can also skip these, but that's of course a bit pointless. The type will be equal to the actual field type. And the name will once again be equal to the original field name. Filter maps that have a Option<T> type have exactly the same signature. Duering query you can call this method multiple times in case you wish to allow multiple variants.

Query Result (e.g. EmployeeInMemDBQueryResult)

fn signature description
EmployeeInMemDBQueryResult::first(&self) -> &Employee return a reference to the first matched employee found. An implementation detail is that this will be the matched row that was first inserted, but for compatibility reasons you best not rely on this if you do not have to.
EmployeeInMemDBQueryResult::any(&self) -> &Employee return a reference to a randomly selected matched employee. The randomness can be relied upon to be fair.
EmployeeInMemDBQueryResult::iter(&self) -> EmployeeInMemDBQueryResultIter` return an iterator for the query result, which will allow you to iterate over all found results, and as such also collect them into an owned data structure should you wish.
EmployeeInMemDBQueryResult::filter<F>(&self, predicate: F) -> Option<#EmployeeInMemDBQueryResult> where F: Fn(&#name) -> bool return Some(_) EmployeeInMemDBQueryResult with the same reference data, but containing (and owning) only the indexes for which the linked row matches arcoding to the given Fn predicate

ā›Ø | Safety

This crate uses #![forbid(unsafe_code)] to ensure everything is implemented in 100% safe Rust.

šŸ¦€ | Compatibility

venndb is developed mostly on MacOS M-Series machines and run in production on a variety of Linux systems. Windows support is not officially guaranteed, but is tested using Github Actions with success.

platform tested test platform
MacOS āœ… M2 (developer laptop) and macos-12 Intel (GitHub Action)
Windows āœ… Windows 2022 (GitHub Action)
Linux āœ… Ubuntu 22.04 (GitHub Action)

Please open a ticket in case you have compatibility issues for your setup/platform. Our goal is not to support all possible platformns in the world, but we do want to support as many as we reasonably can.

Minimum supported Rust version

venndb's MSRV is 1.75.

Using GitHub Actions we also test if venndb on that version still works on the stable and beta versions of rust as well.

šŸ§­ | Roadmap

Please refer to https://github.com/plabayo/venndb/milestones to know what's on the roadmap. Is there something not on the roadmap for the next version that you would really like? Please create a feature request to request it and become a sponsor if you can.

šŸ’¼ | License

This project is dual-licensed under both the MIT license and Apache 2.0 License.

šŸ‘‹ | Contributing

šŸŽˆ Thanks for your help improving the project! We are so happy to have you! We have a contributing guide to help you get involved in the venndb project.

Contributions often come from people who already know what they want, be it a fix for a bug they encountered, or a feature that they are missing. Please do always make a ticket if one doesn't exist already.

It's possible however that you do not yet know what specifically to contribute, and yet want to help out. For that we thank you. You can take a look at the open issues, and in particular:

  • good first issue: issues that are good for those new to the venndb codebase;
  • easy: issues that are seen as easy;
  • mentor available: issues for which we offer mentorship;
  • low prio: low prio issues that have no immediate pressure to be finished quick, great in case you want to help out but can only do with limited time to spare;

In general, any issue not assigned already is free to be picked up by anyone else. Please do communicate in the ticket if you are planning to pick it up, as to avoid multiple people trying to solve the same one.

Should you want to contribure this project but you do not yet know how to program in Rust, you could start learning Rust with as goal to contribute as soon as possible to venndb by using "the Rust 101 Learning Guide" as your study companion. Glen can also be hired as a mentor or teacher to give you paid 1-on-1 lessons and other similar consultancy services. You can find his contact details at https://www.glendc.com/.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in venndb by you, shall be licensed as both MIT and Apache 2.0, without any additional terms or conditions.

Acknowledgements

Special thanks goes to all involved in developing, maintaining and supporting the Rust programming language. Also a big shoutout to the "Write Powerful Rust Macros" book by Sam Van Overmeire, which gave the courage to develop this crate.

Some code was also copied/forked from google/argh, for which thank you, we are big fans of that crate. Go use it if you want to create a CLI App.

šŸ’– | Sponsors

venndb is completely free, open-source software which needs lots of effort and time to develop and maintain.

Support this project by becoming a sponsor. One time payments are accepted at GitHub as well as at "Buy me a Coffee".

Sponsors help us continue to maintain and improve venndb, as well as other Free and Open Source (FOSS) technology. It also helps us to create educational content such as https://github.com/plabayo/learn-rust-101, and other open source frameworks such as https://github.com/plabayo/rama.

Sponsors receive perks and depending on your regular contribution it also allows you to rely on us for support and consulting.

Finally, you can also support us by shopping Plabayo <3 VennDB merchandise šŸ›ļø at https://plabayo.threadless.com/.

Plabayo's Store With VennDB Merchandise

Dependencies