Skip to content

Commit

Permalink
chore: docs + improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-herlemont committed Dec 18, 2023
1 parent e7da09d commit e60da94
Show file tree
Hide file tree
Showing 34 changed files with 997 additions and 126 deletions.
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ members = ["native_db_macro"]
redb = "1.4.0"
native_db_macro = { version = "0.4.3", path = "native_db_macro" }
thiserror = "1.0"
uuid = { version = "1", features = [ "v4"] }
serde = { version = "1.0" }
native_model = { version = "0.4.2" }
doc-comment = "0.3.3"

uuid = { version = "1.6" , optional = true }
chrono = { version = "0.4" , optional = true }

# Optional tokio support
tokio = { version = "1", features = ["sync"], optional = true }
# TODO: channels with futures
# TODO: channels crossbeam

[dev-dependencies]
assert_fs = "1.0"
Expand All @@ -34,6 +37,9 @@ skeptic = "0.13"
tokio = { version = "1.33", features = ["test-util","macros"] }
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
criterion = { version = "0.5.1" }
doc-comment = "0.3.3"
uuid = { version = "1", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }


[features]
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Native DB 🔧🔩
# Native DB

[![Crates.io](https://img.shields.io/crates/v/native_db)](https://crates.io/crates/native_db)
[![Linux/Windows/macOS/Android/iOS (Build/Test/Release)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_and_test_release.yml/badge.svg)](https://github.com/vincent-herlemont/native_db/actions/workflows/build_and_test_release.yml)
Expand All @@ -10,19 +10,19 @@
<!-- ALL-CONTRIBUTORS-BADGE:END -->


Here's a drop-in, fast, embedded database solution for multi-platform applications (server, desktop, mobile). It's focused on maintaining coherence between Rust types and stored data with minimal boilerplate. Enjoy! 😌🍃.
Blazing-fast, embedded database for multi-platform apps (server, desktop, mobile) - a Rust project's dream! 🚀 Sync Rust types effortlessly. Simple, effective, and ready to elevate your project. Get started and thrive! 😌🍃

# Features

- Simple API.
- Support for multiple indexes (primary, secondary, unique, non-unique, optional).
- Simple API 🦀.
- Support for **multiple indexes** (primary, secondary, unique, non-unique, optional).
- Minimal boilerplate.
- Transparent serialization/deserialization using [native_model](https://crates.io/crates/native_model).
- Automatic model migration.
- Thread-safe and fully ACID-compliant transactions provided by [redb](https://github.com/cberner/redb).
- Real-time subscription with filters for `insert`, `update` and `delete` operations.
- **Automatic model migration** 🌟.
- **Thread-safe** and fully **ACID-compliant** transactions provided by [redb](https://github.com/cberner/redb).
- **Real-time** subscription with filters for `insert`, `update` and `delete` operations.
- Compatible with all Rust types (`enum`, `struct`, `tuple` etc.).
- Hot snapshots.
- **Hot snapshots**.

# Installation

Expand Down
10 changes: 8 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ build_no_default:
build_default:
cargo build

build_all: build_no_default build_default
build_with_optional:
cargo build -F chrono -F uuid -F tokio

build_all: build_no_default build_default build_with_optional

test_no_default:
cargo test --no-default-features

test_default:
cargo test

test_all: test_no_default test_default
test_with_optional:
cargo test -F chrono -F uuid -F tokio

test_all: test_no_default test_default test_with_optional


expand test_file_name:
Expand Down
117 changes: 64 additions & 53 deletions src/database.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::builder::ModelBuilder;
use crate::database_builder::ModelBuilder;
use crate::db_type::Result;
use crate::stats::{Stats, StatsTable};
use crate::table_definition::PrimaryTableDefinition;
Expand All @@ -15,7 +15,7 @@ use std::sync::atomic::AtomicU64;
use std::sync::{Arc, RwLock};
use std::u64;

/// The [Database] is the main entry point to interact with the database.
/// The database instance. Allows you to create [rw_transaction](database/struct.Database.html#method.rw_transaction) and [r_transaction](database/struct.Database.html#method.r_transaction), [watch](database/struct.Database.html#method.watch) queries, and [unwatch](database/struct.Database.html#method.unwatch) etc.
///
/// # Example
/// ```rust
Expand All @@ -38,24 +38,85 @@ pub struct Database<'a> {
pub(crate) watchers_counter_id: AtomicU64,
}

impl Database<'_> {
/// Creates a new read-write transaction.
pub fn rw_transaction(&self) -> Result<RwTransaction> {
let rw = self.instance.begin_write()?;
let write_txn = RwTransaction {
watcher: &self.watchers,
batch: RefCell::new(watch::Batch::new()),
internal: InternalRwTransaction {
redb_transaction: rw,
primary_table_definitions: &self.primary_table_definitions,
},
};
Ok(write_txn)
}

/// Creates a new read-only transaction.
pub fn r_transaction(&self) -> Result<RTransaction> {
let txn = self.instance.begin_read()?;
let read_txn = RTransaction {
internal: InternalRTransaction {
redb_transaction: txn,
table_definitions: &self.primary_table_definitions,
},
};
Ok(read_txn)
}
}

impl Database<'_> {
/// Watch queries.
pub fn watch(&self) -> Watch {
Watch {
internal: InternalWatch {
watchers: &self.watchers,
watchers_counter_id: &self.watchers_counter_id,
},
}
}

/// Unwatch the given `id`.
/// You can get the `id` from the return value of [`watch`](Self::watch).
/// If the `id` is not valid anymore, this function will do nothing.
/// If the `id` is valid, the corresponding watcher will be removed.
pub fn unwatch(&self, id: u64) -> Result<()> {
let mut watchers = self.watchers.write().unwrap();
watchers.remove_sender(id);
Ok(())
}
}

impl<'a> Database<'a> {
pub(crate) fn seed_model(&mut self, model_builder: &'a ModelBuilder) {
pub(crate) fn seed_model(&mut self, model_builder: &'a ModelBuilder) -> Result<()> {
let main_table_definition =
redb::TableDefinition::new(model_builder.model.primary_key.unique_table_name.as_str());
let mut primary_table_definition: PrimaryTableDefinition =
(model_builder, main_table_definition).into();

let rw = self.instance.begin_write()?;
rw.open_table(primary_table_definition.redb.clone())?;

for secondary_key in model_builder.model.secondary_keys.iter() {
primary_table_definition.secondary_tables.insert(
secondary_key.clone(),
redb::TableDefinition::new(secondary_key.unique_table_name.as_str()).into(),
);
rw.open_table(
primary_table_definition.secondary_tables[&secondary_key]
.redb
.clone(),
)?;
}
rw.commit()?;

self.primary_table_definitions.insert(
model_builder.model.primary_key.unique_table_name.clone(),
primary_table_definition,
);

Ok(())
}

pub fn redb_stats(&self) -> Result<Stats> {
Expand Down Expand Up @@ -113,53 +174,3 @@ impl<'a> Database<'a> {
})
}
}

impl Database<'_> {
/// Creates a new read-write transaction.
pub fn rw_transaction(&self) -> Result<RwTransaction> {
let rw = self.instance.begin_write()?;
let write_txn = RwTransaction {
watcher: &self.watchers,
batch: RefCell::new(watch::Batch::new()),
internal: InternalRwTransaction {
redb_transaction: rw,
primary_table_definitions: &self.primary_table_definitions,
},
};
Ok(write_txn)
}

/// Creates a new read-only transaction.
pub fn r_transaction(&self) -> Result<RTransaction> {
let txn = self.instance.begin_read()?;
let read_txn = RTransaction {
internal: InternalRTransaction {
redb_transaction: txn,
table_definitions: &self.primary_table_definitions,
},
};
Ok(read_txn)
}
}

impl Database<'_> {
/// Watch queries.
pub fn watch(&self) -> Watch {
Watch {
internal: InternalWatch {
watchers: &self.watchers,
watchers_counter_id: &self.watchers_counter_id,
},
}
}

/// Unwatch the given `id`.
/// You can get the `id` from the return value of [`primary_watch`](#method.primary_watch).
/// If the `id` is not valid anymore, this function will do nothing.
/// If the `id` is valid, the corresponding watcher will be removed.
pub fn unwatch(&self, id: u64) -> Result<()> {
let mut watchers = self.watchers.write().unwrap();
watchers.remove_sender(id);
Ok(())
}
}
17 changes: 9 additions & 8 deletions src/builder.rs → src/database_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::path::Path;
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, RwLock};

/// Builder for the [`Db`](super::Database) instance.
/// Builder that allows you to create a [`Database`](crate::Database) instance via [`create`](Self::create) or [`open`](Self::open) etc. and [define](Self::define) models.
pub struct DatabaseBuilder {
cache_size_bytes: Option<usize>,
models_builder: HashMap<String, ModelBuilder>,
Expand All @@ -21,7 +21,7 @@ impl DatabaseBuilder {
redb_builder
}

fn init<'a>(&'a self, redb_database: redb::Database) -> Database<'a> {
fn init<'a>(&'a self, redb_database: redb::Database) -> Result<Database<'a>> {
let mut database = Database {
instance: redb_database,
primary_table_definitions: HashMap::new(),
Expand All @@ -30,10 +30,10 @@ impl DatabaseBuilder {
};

for (_, model_builder) in &self.models_builder {
database.seed_model(&model_builder);
database.seed_model(&model_builder)?;
}

database
Ok(database)
}
}

Expand All @@ -58,22 +58,23 @@ impl DatabaseBuilder {
pub fn create(&self, path: impl AsRef<Path>) -> Result<Database> {
let db = self.new_rdb_builder().create(path)?;
// Ok(Self::from_redb(db))
Ok(self.init(db))
self.init(db)
}

/// Similar to [redb::Builder::open(...)](https://docs.rs/redb/latest/redb/struct.Builder.html#method.open)
pub fn open(&self, path: impl AsRef<Path>) -> Result<Database> {
let db = self.new_rdb_builder().open(path)?;
// Ok(Self::from_redb(db))
Ok(self.init(db))
self.init(db)
}

/// Creates a new [`Database`](crate::Database) instance in memory.
pub fn create_in_memory(&self) -> Result<Database> {
let in_memory_backend = redb::backends::InMemoryBackend::new();
let db = self.new_rdb_builder();
let db = db.create_with_backend(in_memory_backend)?;
// Ok(Self::from_redb(db))
Ok(self.init(db))
self.init(db)
}

/// Defines a table using the given model.
Expand All @@ -95,7 +96,7 @@ impl DatabaseBuilder {
/// - `#[primary_key]` on the field
/// - `#[native_db(primary_key(<method_name>))]` on any type `enum`, `struct`, `tuple struct` or `unit struct`.
///
/// By default is **unique** so you can't have two instances of the model with the same primary key saved in the database.
/// The primary key is **unique**, so you can't have two instances of the model with the same primary key saved in the database.
///
/// ## Define a simple model with a primary key
/// ```rust
Expand Down
38 changes: 38 additions & 0 deletions src/db_type/key/inner_key_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,44 @@ impl_inner_key_value_for_primitive!(i128);
impl_inner_key_value_for_primitive!(f32);
impl_inner_key_value_for_primitive!(f64);

// Implement Uuid::uuid

#[cfg(feature = "uuid")]
impl InnerKeyValue for uuid::Uuid {
fn database_inner_key_value(&self) -> DatabaseInnerKeyValue {
DatabaseInnerKeyValue::new(self.as_bytes().to_vec())
}
}

#[cfg(feature = "uuid")]
impl InnerKeyValue for &uuid::Uuid {
fn database_inner_key_value(&self) -> DatabaseInnerKeyValue {
DatabaseInnerKeyValue::new(self.as_bytes().to_vec())
}
}

// Implement chrono::DateTime<TZ>

#[cfg(feature = "chrono")]
impl<TZ> InnerKeyValue for chrono::DateTime<TZ>
where
TZ: chrono::TimeZone,
{
fn database_inner_key_value(&self) -> DatabaseInnerKeyValue {
DatabaseInnerKeyValue::new(self.timestamp().to_be_bytes().to_vec())
}
}

#[cfg(feature = "chrono")]
impl<TZ> InnerKeyValue for &chrono::DateTime<TZ>
where
TZ: chrono::TimeZone,
{
fn database_inner_key_value(&self) -> DatabaseInnerKeyValue {
DatabaseInnerKeyValue::new(self.timestamp().to_be_bytes().to_vec())
}
}

impl RedbValue for DatabaseInnerKeyValue {
type SelfType<'a> = DatabaseInnerKeyValue;
type AsBytes<'a> = &'a [u8] where Self: 'a;
Expand Down
19 changes: 12 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
//! 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.
mod builder;
//! It supports multiple indexes, real-time watch with filters, model migration, hot snapshot, and more.
//!
//! See [README.md](https://github.com/vincent-herlemont/native_db) for more information.
mod database;
mod database_builder;
pub mod db_type;
mod model;
mod serialization;
mod snapshot;
mod stats;
mod table_definition;

/// All database interactions here,[`r_transaction`](transaction/struct.RTransaction.html), [`rw_transaction`](transaction/struct.RwTransaction.html) and [`query`](transaction/query/index.html).
pub mod transaction;
/// Watch data in real-time.
pub mod watch;

// Re-export
pub use db_type::InnerKeyValue;
pub use db_type::Input;

// Export
pub use builder::*;
pub use database::*;
pub use database_builder::*;
pub use model::*;
pub use native_db_macro::*;
pub use native_db_macro::*;
pub use serialization::*;

#[cfg(doctest)]
#[macro_use]
Expand All @@ -33,3 +34,7 @@ extern crate doc_comment;
doc_comment! {
include_str!("../README.md")
}

/// Macro which link [`native_model`](https://crates.io/crates/native_model) to the Native DB. See [`DatabaseBuilder.define`](struct.DatabaseBuilder.html#method.define) for more information.
pub use native_db_macro::*;
pub use serialization::*;
Loading

0 comments on commit e60da94

Please sign in to comment.