Crate native_db

source
Expand description

Native DB is a Rust library that provides a simple, fast, and embedded database solution, focusing on maintaining coherence between Rust types and stored data with minimal boilerplate. It supports multiple indexes, real-time watch with filters, model migration, hot snapshot, and more.

§Summary

§Api

  • Models - Collection of models. Equivalent to a schema in a traditional database.
    • new - Create a new collection of models.
    • define - Define a model.
  • Builder - Builder to create a database.
  • Database - Database instance.
    • compact - Compact the database.
    • check_integrity - Check the integrity of the database.
    • rw_transaction - Create a read-write transaction.
      • insert - Insert a item, fail if the item already exists.
      • upsert - Upsert a item, update if the item already exists.
      • update - Update a item, replace an existing item.
      • remove - Remove a item, remove an existing item.
      • migrate - Migrate a model, affect all items.
      • commit - Commit the transaction.
      • abort - Abort the transaction.
    • r_transaction - Create a read-only transaction.
      • get - Get a item.
      • scan - Scan items.
        • primary - Scan items by primary key.
          • all - Scan all items.
          • start_with - Scan items with a primary key starting with a key.
          • range - Scan items with a primary key in a given range.
        • secondary - Scan items by secondary key.
          • all - Scan items with a given secondary key.
          • start_with - Scan items with a secondary key starting with a key.
          • range - Scan items with a secondary key in a given range.
      • len - Get the number of items.
        • primary - Get the number of items by primary key.
        • secondary - Get the number of items by secondary key.
    • watch - Watch items in real-time. Works via std channel based or tokio channel based depending on the feature tokio.
      • get - Watch a item.
        • primary - Watch a item by primary key.
        • secondary - Watch a item by secondary key.
      • scan - Watch items.
        • primary - Watch items by primary key.
          • all - Watch all items.
          • start_with - Watch items with a primary key starting with a key.
          • range - Watch items with a primary key in a given range.
        • secondary - Watch items by secondary key.
          • all - Watch items with a given secondary key.
          • start_with - Watch items with a secondary key starting with a key.
          • range - Watch items with a secondary key in a given range.

§Quick Start

We will create a simple example to show how to use the library.

§Create a model

👉 Unlike the usual database where there is a difference between schema and model, here, as we can directly use Rust types that are serialized in the database, we do not have the concept of schema, only that of the model.

In this section, we will create a simple model. I have chosen a particular organization using Rust modules, which I find to be a best practice. However, it is not mandatory; you can do it as you prefer. (see define for more information)

In this example:

  • We create a module data which contains all versions of all models.
  • We create a module v1 which contains the first version of your data, we will put other versions later.
  • We create a type alias Person to the latest version v1::Person, which allows us to use the latest version of the model in the application.
pub mod data {
    use native_db::{native_db, ToKey};
    use native_model::{native_model, Model};
    use serde::{Deserialize, Serialize};

    pub type Person = v1::Person;

    pub mod v1 {
        use super::*;
         
        #[derive(Serialize, Deserialize, Debug)]
        #[native_model(id = 1, version = 1)]
        #[native_db]
        pub struct Person {
           #[primary_key]
           pub name: String,
        }
    }
}

§Create a database

After creating the model in the previous step, we can now create the database with the model.

Note good practices: define the models by specifying each version, in our case data::v1::Person.

use native_db::*;
use once_cell::sync::Lazy;

// Define the models
// The lifetime of the models needs to be longer or equal to the lifetime of the database.
// In many cases, it is simpler to use a static variable but it is not mandatory.
static MODELS: Lazy<Models> = Lazy::new(|| {
   let mut models = Models::new();
   // It's a good practice to define the models by specifying the version
   models.define::<data::v1::Person>().unwrap();
   models
});

fn main() -> Result<(), db_type::Error> {
    // Create the database
    let db = Builder::new().create_in_memory(&MODELS)?;
    Ok(())
}

§Insert a model in the database

Note a good practice: use the latest version of the model in your application. In our case, we use data::Person.

use native_db::*;
use once_cell::sync::Lazy;

fn main() -> Result<(), db_type::Error> {
    // ... database creation see previous example

    // Insert a person
    let rw = db.rw_transaction()?;
    // It's a good practice to use the latest version in your application
    rw.insert(data::Person { name: "Alice".to_string() })?;
    rw.commit()?;

    // Get the person
    let r = db.r_transaction()?;
    let person: data::Person = r.get().primary("Alice".to_string())?.unwrap();
    assert_eq!(person.name, "Alice");
    Ok(())
}

§Update a model

We need to add the field age to the Person model, but data is already stored in the database so we need to migrate it.

To do this we have to:

  • Create a version v2 of the model Person with the new field age.
  • Implement the From (or TryFrom) trait for the previous version v1 to the new version v2, so we can migrate the data. See native_model#Data model for more information.
pub mod data {
    // ... same imports
     
    // Update the type alias to the latest version
    pub type Person = v2::Person;

    pub mod v1 {
         // ... the previous version of Person
         
        impl From<v2::Person> for Person {
           fn from(p: v2::Person) -> Self {
              Self {
                 name: p.name,
              }
           }
        }
    }

    pub mod v2 {
        use super::*;

        #[derive(Serialize, Deserialize, Debug)]
        #[native_model(id = 1, version = 2, from = v1::Person)]
        #[native_db]
        pub struct Person {
           #[primary_key]
           pub name: String,
           pub age: u8,
        }
         
        impl From<v1::Person> for Person {
           fn from(p: v1::Person) -> Self {
              Self {
                 name: p.name,
                 age: 0,
              }
           }
        }
    }
}

§Migration

After updating the model, we need to define the new version v2 of the model Person and migrate the data.

use native_db::*;
use once_cell::sync::Lazy;

static MODELS: Lazy<Models> = Lazy::new(|| {
   let mut models = Models::new();
   // Define the models by specifying the version
   models.define::<data::v1::Person>().unwrap();
   models.define::<data::v2::Person>().unwrap();
   models
});

fn main() -> Result<(), db_type::Error> {
    // Create the database
    let db = Builder::new().create_in_memory(&MODELS)?;

    // Migrate the data in a transaction
    let rw = db.rw_transaction()?;
    rw.migrate::<data::Person>()?;
    rw.commit()?;

    // Now we can insert a person with the new field age ...

    Ok(())
}

More details migrate method.

Re-exports§

Modules§

Structs§

Functions§

Attribute Macros§

Derive Macros§