#bit-flags #queried #rows #columns #query #in-memory

flagdb

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

1 unstable release

0.0.0 Mar 27, 2023

#6 in #queried

MIT license

99KB

flagdb banner

Build Status Crates.io MIT licensed

Github Repository | API Docs

An 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.

It is developed mostly to scratch our own itch and there are certainly viable alternatives available.

Contributions and feedback are welcome.

Example

Read Only Database Example

use flagdb::flagdb;

flagdb! {
    struct Employee {
        id: u32,
        name: String,
        is_manager: bool,
        is_admin: bool,
        is_active: bool,
        department: enum {
            Sales,
            Marketing,
            Engineering,
            HumanResources,
            Accounting,
        }
    }
}

fn main() {
    let db = EmployeeDB::with_capacity(100).extend(vec![
        Employee {
            id: 1,
            name: "John".to_string(),
            is_manager: true,
            is_admin: false,
            is_active: true,
            department: Department::Sales,
        },
        Employee {
            id: 2,
            name: "Jane".to_string(),
            is_manager: false,
            is_admin: true,
            is_active: true,
            department: Department::Marketing,
        },
        Employee {
            id: 3,
            name: "Bob".to_string(),
            is_manager: false,
            is_admin: false,
            is_active: false,
            department: Department::Engineering,
        },
    ]);

    let active_managers: Vec<String> = db.query()
        .is_manager()
        .is_active()
        .run()
        .map(|employee| employee.name)
        .collect();

    assert_eq!(active_managers, vec!["John".to_string()]);
}

The above example show how easy it is to create an in-memory database with little effort while still being powerful and easy to use.

What's going om under the hood? High level the following is generated (use cargo expand to see the fully generated code):

#[derive(Debug, flagdb::Serialize, flagdb::Deserialize)]
struct Employee {
    pub id: u32,
    pub name: String,
    pub is_manager: bool,
    pub is_admin: bool,
    pub is_active: bool,
    pub department: EmployeeDepartment,
}

#[derive(Debug, flagdb::Serialize, flagdb::Deserialize)]
enum EmployeeDepartment {
    Sales,
    Marketing,
    Engineering,
    HumanResources,
    Accounting,
}

#[derive(Debug)]
struct EmployeeDB {
    employees: Vec<Employee>,
    is_manager: flagdb::BitVec,
    is_admin: flagdb::BitVec,
    is_active: flagdb::BitVec,
    departments: flagdb::BitMap<EmployeeDepartment>,
}

struct EmployeeDBQueryBuilder<'a> {
    db: &'a EmployeeDB,

    ...
}

impl EmployeeDB {
    fn new() -> Self { ... }
    fn with_capacity(capacity: usize) -> Self { ... }

    fn extend(&mut self, employees: impl IntoIterator<Item = Employee>) { ... }

    fn query(&self) -> EmployeeDBQueryBuilder { ... }

    fn iter(&self) -> impl Iterator<Item = &Employee> {
        self.employees.iter()
    }
}

impl EmployeeDBQueryBuilder<'_> {
    fn is_manager(&mut self) -> &mut Self { ... }
    fn is_admin(&mut self) -> &mut Self { ... }
    fn is_active(&mut self) -> &mut Self { ... }
    fn department(&mut self, department: EmployeeDepartment) -> &mut Self { ... }

    fn run(self) -> impl Iterator<Item = &Employee> { ... }
}

impl Iterator for EmployeeDB<'_> {
    type Item = &'_ Employee;

    fn next(&mut self) -> Option<Self::Item> { ... }
}

impl IntoIterator for EmployeeDB {
    type Item = Employee;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.employees.into_iter()
    }
}

Mutate Example

Should you need to mutate the database, you can do so with a couple of changes.

First the flagdb creation has to be change a bit:

flagdb! { struct Employee { #flagdb::key id: u32, name: String, is_manager: bool, is_admin: bool, is_active: bool, department: enum { Sales, Marketing, Engineering, HumanResources, Accounting, } } }

Note the use of the #[flagdb::key] attribute on the id field, which will ensure that internally we generate a flagdb::FxHashMap<u32, usize> property in the actual EmployeeDB struct.

Then we can mutate previously queried employees as follows:

db.get_mut(1).unwrap().is_manager = false; // John is no longer a manager

Without modifications we can however also query mutable:

// fire all the managers
let active_managers: Vec<&mut Employee> = db.mutate()
    .is_manager()
    .is_active()
    .run()
    .map(|employee| employee.is_active = false);

Safety

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

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 flagdb project.

License

This project is licensed under the MIT license.

Contribution

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

No runtime deps