init
Some checks failed
ci/woodpecker/push/test Pipeline failed

This commit is contained in:
JMARyA 2025-04-28 18:53:21 +02:00
commit 6c54873ca2
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
34 changed files with 5502 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/target
.env
/db
/files
/owl_macro/target

9
.woodpecker/test.yml Normal file
View file

@ -0,0 +1,9 @@
when:
- event: push
branch: main
steps:
- name: "Cargo Test"
image: rust:alpine
commands:
- cargo test --all

2730
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

27
Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "owl"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = { version = "0.4.38", features = ["serde"] }
log = "0.4.20"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
tokio = { version = "1.35.1", features = ["full"] }
uuid = { version = "1.8.0", features = ["v4", "serde"] }
sha2 = "0.10.8"
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio-native-tls", "derive", "uuid", "chrono", "json"] }
geozero = "0.14.0"
ulid = { version = "1.2.1", features = ["serde"] }
owl_macro = { path = "./owl_macro" }
vfs = "0.12.1"
dashmap = "6.1.0"
rayon = "1.10.0"
argh = "0.1.13"
rmp-serde = "1.3.0"
rmpv = { version = "1.3.0", features = ["serde", "with-serde"] }
env_logger = "0.11.8"
parking_lot = { version = "0.12.3", features = ["send_guard"] }
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
once_cell = "1.21.3"

17
Dockerfile Normal file
View file

@ -0,0 +1,17 @@
FROM rust:buster as builder
COPY . /app
WORKDIR /app
RUN cargo build --release
FROM debian:buster
RUN apt update && apt upgrade -y
RUN apt install -y ca-certificates openssl
COPY --from=builder /app/target/release/owl /owl
WORKDIR /
CMD ["/owl"]

39
README.md Normal file
View file

@ -0,0 +1,39 @@
# 🦉 owl
owl provides a model based database with references and relations.
## Example
Simple embedded database:
```rust
use owl::prelude::*;
#[model]
#[derive(Debug)]
pub struct Item {
pub id: Id,
pub cost: f64,
pub strength: f64
}
pub fn main() {
// Init
let db = Database::in_memory();
// Save
let item = Item {
id: Id::new_ulid(),
cost: 1.20,
strength: 0.4,
};
dbg!(&item);
db.save(&item);
// Get
let i: Item = db.get(&item.id.to_string());
dbg!(i);
}
```
For more usage examples look at the `./examples` directory.

81
examples/basic.rs Normal file
View file

@ -0,0 +1,81 @@
use owl::prelude::*;
#[model]
#[derive(Debug)]
pub struct Item {
pub id: Id,
pub cost: f64,
pub strength: f64,
}
pub fn main() {
// Init
let db = Database::in_memory();
// Save
let item = Item {
id: Id::new_ulid(),
cost: 1.80,
strength: 0.4,
};
let first_id = item.id.clone();
dbg!(&item);
let item = db.save(item);
// Get
let i: Model<Item> = db.get(item.read().id.clone()).unwrap();
dbg!(&i.read());
db.save(Item {
id: Id::new_ulid(),
cost: 0.3,
strength: 2.4,
});
db.save(Item {
id: Id::new_ulid(),
cost: 3.4,
strength: 0.4,
});
db.save(Item {
id: Id::new_ulid(),
cost: 20.0,
strength: 200.5,
});
db.save(Item {
id: Id::new_ulid(),
cost: 4.2,
strength: 4.2,
});
// Query
let res = db.query(|x: &Item| x.cost > 1.5);
dbg!(&res
.into_iter()
.map(|x| format!("{:?}", x.read()))
.collect::<Vec<_>>());
// Update
db.update(&mut db.query(|x: &Item| x.cost > 1.5), |x: &mut Item| {
x.cost += 1.0;
});
let item: Model<Item> = db.get(first_id.to_string().as_str()).unwrap();
dbg!(&item.read());
assert_eq!(item.read().cost, 2.80);
// Aggregates
let count = db.query(|x: &Item| x.cost > 1.5).len();
let sum: f64 = db
.query(|x: &Item| x.cost > 1.5)
.iter()
.map(|x| x.read().cost)
.sum();
dbg!(count);
dbg!(sum);
}

81
examples/basic_global.rs Normal file
View file

@ -0,0 +1,81 @@
use owl::{get, prelude::*, query, save, set_global_db, update};
#[model]
#[derive(Debug)]
pub struct Item {
pub id: Id,
pub cost: f64,
pub strength: f64,
}
pub fn main() {
// Init
let db = Database::in_memory();
set_global_db!(db);
// Save
let item = Item {
id: Id::new_ulid(),
cost: 1.80,
strength: 0.4,
};
let first_id = item.id.clone();
dbg!(&item);
let item = save!(item);
// Get
let i: Model<Item> = get!(item.read().id.clone()).unwrap();
dbg!(&i.read());
save!(Item {
id: Id::new_ulid(),
cost: 0.3,
strength: 2.4,
});
save!(Item {
id: Id::new_ulid(),
cost: 3.4,
strength: 0.4,
});
save!(Item {
id: Id::new_ulid(),
cost: 20.0,
strength: 200.5,
});
save!(Item {
id: Id::new_ulid(),
cost: 4.2,
strength: 4.2,
});
// Query
let res = query!(|x: &Item| x.cost > 1.5);
dbg!(&res
.into_iter()
.map(|x| format!("{:?}", x.read()))
.collect::<Vec<_>>());
// Update
update!(&mut query!(|x: &Item| x.cost > 1.5), |x: &mut Item| {
x.cost += 1.0;
});
let item: Model<Item> = get!(first_id.to_string().as_str()).unwrap();
dbg!(&item.read());
assert_eq!(item.read().cost, 2.80);
// Aggregates
let count = query!(|x: &Item| x.cost > 1.5).len();
let sum: f64 = query!(|x: &Item| x.cost > 1.5)
.iter()
.map(|x| x.read().cost)
.sum();
dbg!(count);
dbg!(sum);
}

17
examples/blob.rs Normal file
View file

@ -0,0 +1,17 @@
use owl::{db::model::file::File, prelude::*, Identifiable};
pub fn main() {
// Init
let db = Database::in_memory();
let f = File::new(
include_bytes!("../Cargo.toml").to_vec(),
Some("Cargo.toml".to_string()),
&db,
);
dbg!(&f);
let f: Model<File> = db.get(f.id()).unwrap();
let data = String::from_utf8(f.read_file(&db)).unwrap();
println!("Content: {data}");
}

80
examples/friends.rs Normal file
View file

@ -0,0 +1,80 @@
use owl::{
db::{
model::person::Person,
relation::{clean_graph_traversal, find_path, get_other},
},
prelude::*,
};
#[relation("friend", Person, "friendy", Person, RelationKind::Unidirectional)]
pub struct Friendship;
pub fn main() {
env_logger::init();
let db = Database::filesystem("./db");
let alice = Person::new_id("alice", "", "");
let alice = db.save(alice);
let bob = Person::new_id("bob", "", "");
let bob = db.save(bob);
Friendship {}.add(&alice, &bob, None, &db);
let charizard = Person::new_id("charizard", "", "");
let charizard = db.save(charizard);
Friendship.add(&alice, &charizard, None, &db);
Friendship.add(&charizard, &bob, None, &db);
let pika = db.save(Person::new_id("pika", "", ""));
Friendship.add(&pika, &charizard, None, &db);
let malice = db.save(Person::new_id("malice", "", ""));
Friendship.add(&pika, &malice, None, &db);
let drache = db.save(Person::new_id("drache", "", ""));
Friendship.add(&drache, &bob, None, &db);
Friendship.add(&drache, &malice, None, &db);
let enid = db.save(Person::new_id("enid", "", ""));
Friendship.add(&enid, &alice, None, &db);
print_friends("person::alice", &db);
print_friends("person::bob", &db);
print_friends("person::charizard", &db);
print_friends("person::drache", &db);
print_friends("person::enid", &db);
print_friends("person::malice", &db);
println!(
"alice to malice? - {:?}",
clean_graph_traversal(
"person::alice",
&find_path(
"person::alice".into(),
"person::malice".into(),
6,
|id, db| get_friends_of(id, &db),
&db
)
)
);
}
pub fn print_friends(id: &str, db: &Database) {
let other: Vec<_> = get_friends_of(id, db);
println!("friends of {id} -- {other:?}");
}
pub fn get_friends_of<T: Into<IdRef<Person>>>(id: T, db: &Database) -> Vec<IdRef<Person>> {
let id: IdRef<Person> = id.into();
let refs = Friendship::get_friend_of(id.to_string(), db);
dbg!(&refs);
get_other::<Person, _, _>(id, refs, db)
.into_iter()
.map(|x| IdRef::<Person>::from(&x))
.collect()
}

55
examples/loan.rs Normal file
View file

@ -0,0 +1,55 @@
use owl::{db::model::person::Person, prelude::*};
#[relation("receiver", Person, "payer", Person, RelationKind::Bidirectional)]
pub struct LoanRelation {
debt: f64,
}
pub fn lend_money(who: &Model<Person>, from: &Model<Person>, amount: f64, db: &Database) {
LoanRelation { debt: amount }.add(who, from, None, db)
}
pub fn pay_off(who: &Model<Person>, to: &Model<Person>, amount: f64, db: &Database) {
let mut loan = LoanRelation::get(who, to, db).unwrap();
loan.write(db, |loan| {
loan.alter_meta(|x: &mut _| {
x.debt -= amount;
});
});
}
pub fn main() {
env_logger::init();
let db = Database::filesystem("./db");
let p = db.save(Person::new_id("myperson", "first", "last"));
let p2 = db.save(Person::new_id("secperson", "second", "last"));
let banker = db.save(Person::new_id("banker", "boss", "bank"));
lend_money(&p, &banker, 250.0, &db);
lend_money(&p2, &p, 100.0, &db);
lend_money(&p2, &banker, 150.0, &db);
let financer: Vec<_> = LoanRelation::get_payer_of(&p2, &db)
.into_iter()
.map(|x| LoanRelation::payer(&x.dereference(&db), &db))
.map(|x| x.read().first_name.current().cloned().unwrap())
.collect();
println!(
"{} is financed by {:?}",
p2.read().first_name.current().unwrap(),
financer
);
let brokers = LoanRelation::get_receiver_of(&p, &db);
dbg!(&brokers);
let brokers = dereference(&brokers, &db);
brokers.iter().for_each(|x| println!("{:?}", x.read()));
pay_off(&p2, &p, 100.0, &db);
let brokers = dereference(&LoanRelation::get_receiver_of(&p, &db), &db);
brokers.iter().for_each(|x| println!("{:?}", x.read()));
}

29
examples/parent.rs Normal file
View file

@ -0,0 +1,29 @@
use owl::{db::model::person::Person, prelude::*};
#[relation("parent", Person, "child", Person, RelationKind::Unidirectional)]
pub struct ParentRelation;
pub fn main() {
let db = Database::in_memory();
let p = Person::new_id("myperson", "first", "last");
db.save(p);
let p: Model<Person> = db.get("myperson").unwrap();
let p2 = Person::new_id("secperson", "second", "last");
let p2 = db.save(p2);
ParentRelation.add(&p, &p2, None, &db);
let children_of = ParentRelation::get_child_of(&p, &db);
dbg!(&children_of);
let children_of = dereference(&children_of, &db);
children_of
.iter()
.for_each(|x| println!("child: {:?}", x.read()));
let my_parents = dereference(&ParentRelation::get_parent_of(&p2, &db), &db);
my_parents
.iter()
.for_each(|x| println!("parent: {:?}", x.read()));
}

61
examples/references.rs Normal file
View file

@ -0,0 +1,61 @@
use owl::{db::model::person::Person, prelude::*};
#[model]
#[derive(Debug)]
pub struct Car {
pub id: Id,
pub price: u32,
pub driver: IdRef<Person>,
}
pub fn main() {
env_logger::init();
// Init
let db = Database::in_memory();
let per = Person::new_id("perso", "P1", "");
let per2: Person = Person::new_id("perso2", "P2", "");
// Save
let car = Car {
id: Id::new_ulid(),
price: 1000,
driver: per.reference(),
};
db.save(per);
let per2 = db.save(per2);
let mut car = db.save(car);
println!(
"P1 has {} cars",
db.query(|car: &Car| {
car.driver
.dereference(&db)
.read()
.first_name
.current()
.unwrap()
.as_str()
== "P1"
})
.iter()
.count()
);
car.write(&db, |car| {
car.driver = per2.reference();
});
println!(
"P1 has {} cars",
db.query(|car: &Car| {
car.driver
.try_dereference(&db)
.map(|x| x.read().first_name.current().unwrap().as_str() == "P1")
.unwrap_or(false)
})
.iter()
.count()
);
}

43
examples/stock.rs Normal file
View file

@ -0,0 +1,43 @@
use owl::prelude::*;
#[model]
pub struct Stock {
pub id: Id,
}
#[model]
pub struct Owner {
pub id: Id,
}
#[relation("owner", Owner, "stock", Stock, RelationKind::Unidirectional)]
pub struct StockOrder {
amount: f64,
}
pub fn main() {
env_logger::init();
let db = Database::filesystem("./db");
let o = Owner { id: Id::new_ulid() };
let o = db.save(o);
let apl = Stock {
id: Id::String("APL".to_string()),
};
let apl = db.save(apl);
StockOrder { amount: 1.0 }.add(&o, &apl, None, &db);
for order in StockOrder::get_stock_of(&o, &db) {
let rel = db.get(order).unwrap();
println!(
"{} has {} {}",
StockOrder::owner(&rel, &db).read().id.to_string(),
StockOrder::meta(&rel).unwrap().amount,
StockOrder::stock(&rel, &db).read().id.to_string()
);
}
}

61
examples/watch.rs Normal file
View file

@ -0,0 +1,61 @@
use owl::prelude::*;
#[model]
#[derive(Debug)]
pub struct Contact {
pub id: Id,
pub age: u16,
}
pub fn age_one_year(p: &mut Model<Contact>, db: &Database) {
p.write(db, |p| p.age += 1);
}
pub fn main() {
env_logger::init();
// Init
let db = Database::in_memory();
let mut c1 = db.save(Contact {
id: Id::String("c1".to_string()),
age: 15,
});
let mut c2 = db.save(Contact {
id: Id::String("c2".to_string()),
age: 17,
});
let mut c3 = db.save(Contact {
id: Id::String("c3".to_string()),
age: 16,
});
let age_18_watcher = db.watch::<Contact, _, _>(
|c| c.age == 18,
|c| {
println!("{} turned 18", c.id);
},
);
std::thread::spawn(move || loop {
age_18_watcher.process();
});
age_one_year(&mut c1, &db);
age_one_year(&mut c2, &db);
age_one_year(&mut c3, &db);
age_one_year(&mut c1, &db);
age_one_year(&mut c2, &db);
age_one_year(&mut c3, &db);
age_one_year(&mut c1, &db);
age_one_year(&mut c2, &db);
age_one_year(&mut c3, &db);
age_one_year(&mut c1, &db);
age_one_year(&mut c2, &db);
age_one_year(&mut c3, &db);
std::thread::sleep(std::time::Duration::from_secs(10));
}

70
owl_macro/Cargo.lock generated Normal file
View file

@ -0,0 +1,70 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "owl_macro"
version = "0.1.0"
dependencies = [
"convert_case",
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"

14
owl_macro/Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "owl_macro"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1"
syn = { version = "2", features = ["full"] }
proc-macro2 = "1"
convert_case = "0.6"
heck = "0.5.0"

215
owl_macro/src/lib.rs Normal file
View file

@ -0,0 +1,215 @@
extern crate proc_macro;
use convert_case::{Case, Casing};
use heck::ToSnakeCase;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::{Parse, ParseStream}, parse_macro_input, ItemStruct, LitStr, Path, Token};
#[proc_macro_attribute]
/// Create a relation.
///
/// Syntax: `#[relation("TopName", Top, "SubName", Sub, RelationKind::*)]`
///
/// # Examples
///
/// ```ignore
/// #[relation("Lover", Person, "Loved", Person, RelationKind::Bidirectional)]
/// pub struct LoveRelation {}
/// ```
pub fn relation(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as RelationArgs);
let input_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &input_struct.ident;
let role1 = &args.role1;
let role1_fn = format_ident!("get_{}_of", &args.role2);
let ty1 = &args.ty1;
let role2 = &args.role2;
let role2_fn = format_ident!("get_{}_of", &args.role1);
let ty2 = &args.ty2;
let kind = &args.kind;
let struct_ref_name = format_ident!("{}Reference", struct_name);
let ref_name = format!(
"{}-refs",
format_ident!("{}", struct_name).to_string().to_snake_case()
);
let fns = match quote!(#kind).to_string().as_str() {
"RelationKind :: Bidirectional" => { quote! {
pub fn #role1_fn(#role1: &owl::db::Model<#ty1>, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_top(#role1, &*db.storage)
}
pub fn #role2_fn(#role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_sub(#role2, &*db.storage)
}
} }
"RelationKind :: Unidirectional" => { quote! {
pub fn #role1_fn<X: Into<owl::db::relation::IdRef<#ty1>>>(#role1: X, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_with_t(#role1, &*db.storage)
}
pub fn #role2_fn<X: Into<owl::db::relation::IdRef<#ty2>>>(#role2: X, db: &owl::db::Database) -> Vec<owl::db::relation::IdRef<#struct_ref_name>> {
Self::relation().get_all_with_s(#role2, &*db.storage)
}
} }
_ => { unimplemented!() }
};
let relation_name = struct_name.to_string().to_case(Case::Snake);
let expanded = quote! {
#[derive(owl::Serialize, owl::Deserialize, Default)]
#input_struct
#[derive(owl::Deserialize, owl::Serialize, Debug)]
pub struct #struct_ref_name {
pub id: Id,
pub inner: owl::db::relation::RelationReference
}
impl owl::Identifiable for #struct_ref_name {
fn id(&self) -> Id {
Id::String(format!("[{}->{}]", self.inner.top.id, self.inner.sub.id))
}
fn model_id() -> String {
#ref_name.to_string()
}
}
impl #struct_ref_name {
pub fn alter_meta<F: Fn(&mut #struct_name)>(
&mut self,
u: F
) {
self.inner.alter_meta(u);
}
}
impl owl::db::relation::RelationRef for #struct_ref_name {
fn top(&self) -> IdRef<serde_json::Value> {
self.inner.top.clone()
}
fn sub(&self) -> IdRef<serde_json::Value> {
self.inner.sub.clone()
}
fn from_ref(r: owl::db::relation::RelationReference) -> Self {
Self {
id: owl::db::id::Id::String(r.id()),
inner: r
}
}
fn update_meta(&mut self, weight: Option<f64>, meta: Option<serde_json::Value>) {
self.inner.weight = weight;
self.inner.meta = meta;
}
}
impl #struct_name {
pub fn relation() -> Relation<#ty1, #ty2, #struct_ref_name, #kind> {
owl::db::relation::Relation::new(#relation_name)
}
#fns
pub fn meta(reference: &Model<#struct_ref_name>) -> Option<Self> {
if let Some(meta) = reference.read().inner.meta.clone() {
return Some(serde_json::from_value(meta).unwrap());
}
None
}
pub fn #role1(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty1> {
unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::top(reference, &db) }
}
pub fn #role2(reference: &Model<#struct_ref_name>, db: &owl::db::Database) -> Model<#ty2> {
unsafe { owl::db::relation::Relation::<#ty1, #ty2, #struct_ref_name, #kind>::sub(reference, &db) }
}
pub fn get(#role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, db: &owl::db::Database) -> Option<Model<#struct_ref_name>> {
unsafe { Self::relation().get(#role1, #role2, &db) }
}
pub fn add(&self, #role1: &owl::db::Model<#ty1>, #role2: &owl::db::Model<#ty2>, weight: Option<f64>, db: &owl::db::Database) {
let meta = serde_json::to_value(self.clone()).ok();
Self::relation().add(#role1, #role2, weight, meta, &db)
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
/// Create a model.
///
/// # Examples
///
/// ```ignore
/// #[model]
/// pub struct MyTing {
/// pub id: Id
/// }
/// ```
pub fn model(_: TokenStream, input: TokenStream) -> TokenStream {
let input_struct = parse_macro_input!(input as ItemStruct);
let struct_name = &input_struct.ident;
let relation_name = struct_name.to_string().to_case(Case::Snake);
let expanded = quote! {
#[derive(serde::Serialize, serde::Deserialize)]
#input_struct
impl owl::db::store::Saveable for #struct_name {}
impl owl::Identifiable for #struct_name {
fn id(&self) -> owl::db::id::Id {
self.id.clone()
}
fn model_id() -> String {
#relation_name.to_string()
}
}
};
TokenStream::from(expanded)
}
struct RelationArgs {
role1: syn::Ident,
ty1: syn::Path,
role2: syn::Ident,
ty2: syn::Path,
kind: syn::Path,
}
impl Parse for RelationArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let role1: LitStr = input.parse()?;
input.parse::<Token![,]>()?;
let ty1: Path = input.parse()?;
input.parse::<Token![,]>()?;
let role2: LitStr = input.parse()?;
input.parse::<Token![,]>()?;
let ty2: Path = input.parse()?;
input.parse::<Token![,]>()?;
let kind: Path = input.parse()?;
Ok(Self {
role1: syn::Ident::new(&role1.value(), role1.span()),
ty1,
role2: syn::Ident::new(&role2.value(), role2.span()),
ty2,
kind,
})
}
}

59
src/cli.rs Normal file
View file

@ -0,0 +1,59 @@
use argh::FromArgs;
#[derive(FromArgs, PartialEq, Debug)]
/// owl cli
pub struct OwlCLI {
#[argh(subcommand)]
pub nested: OwlCLICommands,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
pub enum OwlCLICommands {
List(ListCommand),
Get(GetCommand),
Store(StoreCommand),
}
#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "list")]
pub struct ListCommand {
#[argh(option)]
/// database
pub db: Option<String>,
#[argh(positional)]
/// collection
pub collection: Option<String>,
}
#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "store")]
pub struct StoreCommand {
#[argh(option)]
/// database
pub db: Option<String>,
#[argh(positional)]
/// collection
pub collection: String,
}
#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "get")]
pub struct GetCommand {
#[argh(option)]
/// database
pub db: Option<String>,
#[argh(positional)]
/// collection
pub collection: String,
#[argh(positional)]
/// id
pub id: String,
}

102
src/db/field.rs Normal file
View file

@ -0,0 +1,102 @@
use std::ops::Deref;
use chrono::Utc;
use serde::{Deserialize, Deserializer, Serialize};
// historic field
#[derive(Deserialize, Serialize, Default, Debug, PartialEq)]
pub struct Historic<T> {
pub values: Vec<(chrono::DateTime<Utc>, T)>,
}
impl<T: Clone + Default> Historic<T> {
pub fn alter_or_default<F: Fn(&mut T)>(&mut self, f: F) {
let mut c = if let Some(c) = self.current().cloned() {
c
} else {
T::default()
};
f(&mut c);
self.add(c);
}
}
impl<T: Clone> Deref for Historic<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.current().unwrap()
}
}
impl<T: Clone> Historic<T> {
pub fn new() -> Self {
Self { values: vec![] }
}
pub fn current(&self) -> Option<&T> {
let x = self.values.iter().max_by_key(|(dt, _)| *dt)?;
Some(&x.1)
}
pub fn alter<F: Fn(&mut T)>(&mut self, f: F) {
let mut c = self.current().unwrap().clone();
f(&mut c);
self.add(c);
}
pub fn add(&mut self, value: T) {
self.values.push((chrono::Utc::now(), value));
}
}
pub trait Historicable: Sized {
fn historic(self) -> Historic<Self> {
Historic {
values: vec![(chrono::Utc::now(), self)],
}
}
}
impl Historicable for String {}
/// a unique `Vec<T>`
pub struct UniVec<T>(Vec<T>);
impl<T: PartialEq> UniVec<T> {
pub fn push(&mut self, val: T) -> bool {
if self.0.iter().any(|x| *x == val) {
return false;
}
self.0.push(val);
true
}
}
impl<T: Serialize> Serialize for UniVec<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Vec::<T>::serialize(&self.0, serializer)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for UniVec<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = Vec::deserialize(deserializer)?;
Ok(Self(s))
}
}
impl<T> Deref for UniVec<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

63
src/db/id.rs Normal file
View file

@ -0,0 +1,63 @@
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq)]
/// Database IDs
pub enum Id {
/// String ID
String(String),
/// ULID
ULID(ulid::Ulid),
/// UUID
UUID(uuid::Uuid),
}
impl<'de> Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.parse::<ulid::Ulid>() {
Ok(ulid) => Ok(Id::ULID(ulid)),
Err(_) => match s.parse::<uuid::Uuid>() {
Ok(uuid) => Ok(Id::UUID(uuid)),
Err(_) => Ok(Id::String(s)),
},
}
}
}
impl Serialize for Id {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Id::String(str) => str.serialize(serializer),
Id::ULID(ulid) => ulid.serialize(serializer),
Id::UUID(uuid) => uuid.serialize(serializer),
}
}
}
impl Id {
/// Generate a new random ULID based ID
pub fn new_ulid() -> Self {
Self::ULID(ulid::Ulid::new())
}
}
impl Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Id::String(s) => s.clone(),
Id::ULID(ulid) => ulid.to_string(),
Id::UUID(uuid) => uuid.to_string(),
}
)
}
}

394
src/db/mod.rs Normal file
View file

@ -0,0 +1,394 @@
use parking_lot::{RwLock, RwLockReadGuard};
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use dashmap::DashMap;
use model::file::File;
use relation::{dereference, IdRef};
use serde::{Deserialize, Serialize};
use store::Store;
use crate::Identifiable;
pub mod field;
pub mod id;
pub mod model;
pub mod relation;
pub mod store;
/// really simple query function. it iterates over every object and matches against `predicate`.
///
/// # Examples
///
/// ```ignore
/// let queried = query_t(|x: &MyType| {
/// x.id.to_string().starts_with("something")
/// });
/// ```
pub fn query_t<
T: Identifiable + Serialize + Send + Sync + for<'a> Deserialize<'a> + 'static,
F: Fn(&T) -> bool,
>(
predicate: F,
db: &Database,
) -> Vec<Model<T>> {
let collection = T::model_id();
let all = Store::get_ids(&collection, &*db.storage);
let all: Vec<IdRef<T>> = all
.into_iter()
.map(|x| IdRef::new(format!("{collection}::{x}")))
.collect();
let all = dereference(&all, db);
all.into_iter()
.filter(|x| {
let r = x.read();
let rf = r.deref();
(predicate)(rf)
})
.collect()
}
/// Database Instance
pub struct Database {
/// Underlying storage layer
pub storage: Arc<dyn vfs::FileSystem>,
/// Write models with schema
pub named: bool,
/// Use memory caching
pub cached: bool,
/// Memory Cache
records: Arc<DashMap<String, DashMap<String, Box<dyn ModelObj>>>>,
watcher: Arc<DashMap<String, Vec<crossbeam::channel::Sender<Box<dyn ModelObj>>>>>,
}
pub trait ModelObj: Send + Sync {
fn as_any(&self) -> &dyn std::any::Any;
}
pub struct ModelWatcher<T> {
filter: Box<dyn Fn(&T) -> bool + 'static + Send + Sync>,
apply: Box<dyn Fn(&T) + 'static + Send + Sync>,
recv: crossbeam::channel::Receiver<Box<dyn ModelObj>>,
}
impl<T: 'static + Identifiable> ModelWatcher<T> {
pub fn process(&self) {
if let Ok(val) = self.recv.recv() {
self.handle(val)
}
}
pub fn recv(&self) -> Model<T> {
let res = self.recv.recv().unwrap();
res.as_any().downcast_ref::<Model<T>>().unwrap().clone()
}
pub fn handle(&self, val: Box<dyn ModelObj>) {
log::trace!("Received watch event");
let model: &Model<T> = val.as_any().downcast_ref().unwrap();
if (self.filter)(&model.read()) {
log::info!("Watcher processing {}", model.full_id());
(self.apply)(&model.read());
}
}
pub fn try_process(&self) {
if let Ok(val) = self.recv.recv_timeout(std::time::Duration::from_secs(1)) {
self.handle(val);
}
}
}
impl Clone for Database {
fn clone(&self) -> Self {
Self {
storage: Arc::clone(&self.storage),
named: self.named.clone(),
cached: self.cached.clone(),
records: Arc::clone(&self.records),
watcher: Arc::clone(&self.watcher),
}
}
}
impl Database {
/// Create a new in memory database
pub fn in_memory() -> Self {
Self::ensure_init(Self {
storage: Arc::new(vfs::MemoryFS::new()),
cached: false,
named: false,
records: Arc::new(DashMap::new()),
watcher: Arc::new(DashMap::new()),
})
}
pub fn watch<T, F, G>(&self, filter: F, then: G) -> ModelWatcher<T>
where
T: Identifiable + 'static + Send + Sync,
F: Fn(&T) -> bool + 'static + Send + Sync,
G: Fn(&T) + 'static + Send + Sync,
{
let id = T::model_id();
let (send, recv) = crossbeam::channel::unbounded();
self.watcher.entry(id).or_default().push(send);
ModelWatcher {
filter: Box::new(filter),
apply: Box::new(then),
recv,
}
}
pub fn get_id<T: for<'b> Deserialize<'b>>(&self, collection: &str, id: &str) -> T {
unsafe { Store::get(collection, id, &*self.storage).unwrap() }
}
pub fn named(mut self) -> Self {
self.named = true;
self
}
pub fn with_cache(mut self, choice: bool) -> Self {
self.cached = choice;
self
}
/// Query `Vec<T>` which match `predicate`
pub fn query<
T: Identifiable + for<'b> Deserialize<'b> + Serialize + Send + Sync + 'static,
F: Fn(&T) -> bool,
>(
&self,
predicate: F,
) -> Vec<Model<T>> {
query_t(predicate, self)
}
/// Update every model in `entries` according to `u(_)`
pub fn update<T: 'static + Serialize + Identifiable + Send + Sync, F: Fn(&mut T)>(
&self,
entries: &mut [Model<T>],
u: F,
) {
for e in entries {
e.write(self, &u);
}
}
fn ensure_init(db: Self) -> Self {
let _ = db.storage.create_dir("/collection");
let _ = db.storage.create_dir("/files");
db
}
/// Create a new fs backed database at `path`
pub fn filesystem(path: &str) -> Self {
Self::ensure_init(Self {
storage: Arc::new(vfs::PhysicalFS::new(path)),
named: false,
cached: true,
records: Arc::new(DashMap::new()),
watcher: Arc::new(DashMap::new()),
})
}
/// List all collections
pub fn list(&self) -> Vec<String> {
Store::list(&*self.storage)
}
/// List all entries in `collection`
pub fn list_entries(&self, collection: &str) -> Vec<String> {
Store::get_ids(collection, &*self.storage)
}
pub fn try_cached<T: Identifiable + for<'b> Deserialize<'b> + 'static>(
&self,
col: String,
id: String,
) -> Option<Model<T>> {
// let id = id.trim_start_matches(&format!("{}::", T::model_id()));
let entry = self.records.get(&col)?;
let entry = entry.get(&id)?;
let model: &Model<T> = entry.value().as_any().downcast_ref()?;
Some(model.clone())
}
pub fn save_cache<T: Serialize + Identifiable + Send + Sync + 'static>(
&self,
id: String,
model: Model<T>,
) {
self.records
.entry(T::model_id())
.or_default()
.insert(id, Box::new(model.clone()));
}
/// Get a model `T` from `id`
pub fn get<
T: Identifiable + Serialize + Send + Sync + for<'b> Deserialize<'b> + 'static,
I: Into<IdRef<T>>,
>(
&self,
id: I,
) -> Option<Model<T>> {
let id: IdRef<T> = id.into();
let col = parse_collection(&id.id).unwrap_or(T::model_id());
log::trace!("Getting {id} as {col}");
if self.cached {
if let Some(cached) = self.try_cached(col, id.to_string()) {
log::trace!("Returning {id} from cache");
return Some(cached);
}
}
log::trace!("Trying to load {id}");
let col = parse_collection(&id.to_string());
let col = col.unwrap_or(T::model_id());
let m: T = unsafe {
Store::get(
&col,
&id.id.trim_start_matches(&format!("{}::", col)),
&*self.storage,
)?
};
let model = Model {
inner: Arc::new(RwLock::new(m)),
};
if self.cached {
self.save_cache(id.to_string(), model.clone());
log::trace!("Returning {id} and saved to cache");
}
Some(model)
}
/// Save a raw model into the database
pub unsafe fn save_raw<T: Serialize + Identifiable>(&self, data: &T, collection: &str) {
Store::save(collection, data, self.named, &*self.storage);
}
fn save_model<T: Serialize + Identifiable + Send + Sync + 'static>(&self, data: &Model<T>) {
if let Some(watchers) = self.watcher.get(&T::model_id()) {
log::trace!("Notifying watchers");
for watch in watchers.iter() {
watch.send(Box::new(data.clone())).unwrap();
}
}
let data = data.read();
unsafe {
Store::save(&T::model_id(), data.deref(), self.named, &*self.storage);
}
}
/// Save a model `T` into the database
pub fn save<'a, T: Serialize + Identifiable + Send + Sync + 'static>(
&'a self,
data: T,
) -> Model<T> {
let model = Model {
inner: Arc::new(RwLock::new(data)),
};
self.save_model(&model);
let id = model.full_id().to_string();
if self.cached {
log::trace!("Saving {id} to cache");
self.save_cache(id, model.clone());
}
model
}
}
// TODO : cache eviction based on ref counts
pub struct Model<T> {
inner: Arc<RwLock<T>>,
}
impl Model<File> {
pub fn read_file(&self, db: &Database) -> Vec<u8> {
self.read().read(db)
}
}
impl<T: Identifiable> Model<T> {
pub fn from_raw(raw: T) -> Model<T> {
Model {
inner: Arc::new(RwLock::new(raw)),
}
}
pub fn full_id(&self) -> String {
self.read().full_id()
}
pub fn reference(&self) -> IdRef<T> {
self.read().reference()
}
pub fn read(&self) -> RwLockReadGuard<T> {
self.inner.read()
}
}
impl<T: 'static + Serialize + Identifiable + Send + Sync> Model<T> {
pub fn write<F: Fn(&mut T)>(&mut self, db: &Database, u: F) {
let mut me = self.inner.write();
u(me.deref_mut());
drop(me);
db.save_model(self);
}
/// Update the model inline without writing to `Database`
pub fn write_raw_inline<F: Fn(&mut T)>(&mut self, u: F) {
let mut me = self.inner.write();
u(me.deref_mut());
drop(me);
}
}
impl<T: 'static + Serialize + Identifiable + Send + Sync> ModelObj for Model<T> {
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
impl<T> Clone for Model<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
pub fn parse_collection(id: &str) -> Option<String> {
if id.starts_with("[") {
return None;
}
let splitted: Vec<_> = id.split("[").collect();
if splitted.len() > 1 {
let cols = splitted.first().unwrap();
let col = cols.trim_end_matches("::");
return Some(col.to_string());
}
let mut splitted = id.split("::").collect::<Vec<_>>();
if splitted.len() == 1 {
None
} else {
splitted.pop().unwrap();
Some(splitted.join("::"))
}
}

98
src/db/model/file.rs Normal file
View file

@ -0,0 +1,98 @@
use std::{io::Write, process::Stdio};
use owl_macro::model;
use sha2::{Digest, Sha256};
pub use crate as owl;
use crate::{
db::Database,
prelude::{Id, Saveable},
};
fn sha256(input: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(input);
let result = hasher.finalize();
format!("{:x}", result)
}
/// A generic file
#[derive(Debug, Clone)]
#[model]
pub struct File {
id: Id,
pub name: Option<String>,
pub size: usize,
pub mime: String,
}
pub fn get_mime_type(data: &[u8]) -> String {
let mut child = std::process::Command::new("file")
.arg("--brief")
.arg("--mime-type")
.arg("-") // Read from stdin
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
{
let stdin = child.stdin.as_mut().ok_or("Failed to open stdin").unwrap();
stdin.write_all(data).unwrap();
}
let output = child.wait_with_output().unwrap();
if !output.status.success() {
log::error!("Error getting MIME");
}
let mime_type = String::from_utf8(output.stdout).unwrap().trim().to_string();
mime_type
}
impl File {
pub fn new(data: Vec<u8>, name: Option<String>, db: &Database) -> Self {
let hash = sha256(&data);
let f = Self {
id: Id::String(hash.clone()),
size: data.len(),
mime: get_mime_type(&data),
name,
};
let first: String = hash.chars().take(2).collect();
let second: String = hash.chars().skip(2).take(2).collect();
let _ = db.storage.create_dir(&format!("/files/{first}"));
let _ = db.storage.create_dir(&format!("/files/{first}/{second}"));
db.storage
.create_file(&format!("/files/{first}/{second}/{hash}"))
.unwrap()
.write(&data)
.unwrap();
unsafe {
f.save(false, &*db.storage);
}
f
}
pub fn read(&self, db: &Database) -> Vec<u8> {
let hash = &self.id.to_string();
let first: String = hash.chars().take(2).collect();
let second: String = hash.chars().skip(2).take(2).collect();
let mut buf = Vec::with_capacity(self.size as usize);
db.storage
.open_file(&format!("/files/{first}/{second}/{hash}"))
.unwrap()
.read_to_end(&mut buf)
.unwrap();
buf
}
}

36
src/db/model/location.rs Normal file
View file

@ -0,0 +1,36 @@
use owl_macro::model;
use serde::{Deserialize, Serialize};
pub use crate as owl;
use crate::db::id::Id;
/// Represents a geographical location with an ID, coordinates, and optional address
#[derive(Debug, Clone, PartialEq)]
#[model]
pub struct Location {
pub id: Id,
/// A common name for the `Location`
pub location_name: Option<String>,
/// Geographical coordinates of the location
pub geo: (f64, f64),
/// The street address
pub location_address: Option<String>,
/// The city name
pub city: Option<String>,
/// The state or province name
pub location_state: Option<String>,
/// The postal or ZIP code
pub postal_code: Option<String>,
/// The country name
pub country: Option<String>,
}
/// Represents geographical coordinates with latitude and longitude
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GeoCoordinates {
/// The latitude coordinate
pub latitude: f64,
/// The longitude coordinate
pub longitude: f64,
}

View file

@ -0,0 +1,17 @@
use owl_macro::model;
pub use crate as owl;
use crate::db::id::Id;
/// Represents a mail address with an ID and the address itself
#[derive(Debug, Clone)]
#[model]
pub struct MailAddress {
/// Unique identifier for the mail address
pub id: Id,
/// The actual mail address
pub mail_address: String,
// The domain
pub domain: String,
}

6
src/db/model/mod.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod file;
pub mod location;
pub mod mail_address;
pub mod person;
pub mod phone_number;
pub mod vehicle;

94
src/db/model/person.rs Normal file
View file

@ -0,0 +1,94 @@
use owl_macro::model;
use serde::{Deserialize, Serialize};
pub use crate as owl;
use crate::{
db::{
field::{Historic, Historicable},
id::Id,
query_t,
relation::IdRef,
Database, Model,
},
Identifiable,
};
use super::{location::Location, vehicle::Vehicle};
#[derive(Debug, PartialEq)]
#[model]
pub struct Person {
pub id: Id,
/// The person's first name
pub first_name: Historic<String>,
/// The person's last name
pub last_name: Historic<String>,
/// The person's date of birth.
pub date_of_birth: Option<chrono::NaiveDateTime>,
/// The person's gender.
pub gender: Option<Gender>,
/// Associated locations
pub locations: Historic<Vec<IdRef<Location>>>,
}
#[derive(Deserialize, Serialize, Debug, PartialEq)]
pub enum Gender {
Male,
Female,
Other,
}
impl Person {
pub fn new(first_name: &str, last_name: &str) -> Self {
Self {
id: Id::new_ulid(),
first_name: first_name.to_string().historic(),
last_name: last_name.to_string().historic(),
date_of_birth: None,
gender: None,
locations: Historic::new(),
}
}
pub fn new_id(id: &str, first_name: &str, last_name: &str) -> Self {
Self {
id: Id::String(id.to_string()),
first_name: first_name.to_string().historic(),
last_name: last_name.to_string().historic(),
date_of_birth: None,
gender: None,
locations: Historic::new(),
}
}
pub fn vehicles(&self, db: &Database) -> Vec<Model<Vehicle>> {
query_t(
|vehicle: &Vehicle| {
if let Some(owner) = &vehicle.owned_by {
if *owner == self.reference() {
return true;
}
}
false
},
db,
)
}
/*
pub async fn remove_email(&self, email: uuid::Uuid) {
Relation::remove("relate_person_email", &self.id, &email).await
}pub async fn add_email(&self, email: uuid::Uuid) {
Relation::add("relate_person_email", &self.id, &email).await
}
pub async fn emails(&self) -> Vec<MailAddress> {
Relation::find_relations("relate_person_email", "mail_address", &self.id).await
}
pub async fn phone_numbers(&self) -> Vec<PhoneNumber> {
Relation::find_relations("relate_person_phone_number", "phone_number", &self.id).await
}
*/
}

View file

@ -0,0 +1,14 @@
use owl_macro::model;
pub use crate as owl;
use crate::db::id::Id;
/// Represents a phone number with an ID, the phone number itself, and the country code.
#[derive(Debug, Clone)]
#[model]
pub struct PhoneNumber {
id: Id,
number: String,
country_code: String,
}

16
src/db/model/vehicle.rs Normal file
View file

@ -0,0 +1,16 @@
use owl_macro::model;
pub use crate as owl;
use crate::db::{id::Id, relation::IdRef};
use super::person::Person;
/// A vehicle
#[derive(Debug)]
#[model]
pub struct Vehicle {
pub id: Id,
pub owned_by: Option<IdRef<Person>>,
pub license_plate: Option<String>,
}

589
src/db/relation.rs Normal file
View file

@ -0,0 +1,589 @@
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{Debug, Display};
use crate::Identifiable;
use super::{
id::Id,
store::{Saveable, Store},
Database, Model,
};
#[allow(non_snake_case)]
pub mod RelationKind {
/// Unidirectional relation kind
///
/// The relation is the same for each node.
pub struct Unidirectional;
/// Unidirectional relation kind
///
/// Relations can be directed.
pub struct Bidirectional;
}
/// A relation between `T` and `S` with kind `U`.
/// Additionally with `weight` and `meta` values.
pub struct Relation<T, S, R, U> {
pub name: String,
pub top: std::marker::PhantomData<T>,
pub sub: std::marker::PhantomData<S>,
pub uni: std::marker::PhantomData<U>,
pub references: std::marker::PhantomData<R>,
pub weight: Option<f64>,
pub meta: Option<serde_json::Value>,
}
impl<T, S, R, U> Relation<T, S, R, U> {
/// Create a new relation with `name`
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
weight: None,
meta: None,
references: std::marker::PhantomData,
top: std::marker::PhantomData,
sub: std::marker::PhantomData,
uni: std::marker::PhantomData,
}
}
}
#[derive(PartialEq)]
/// A typed reference type
pub struct IdRef<T> {
pub id: String,
model: std::marker::PhantomData<T>,
}
impl<T> Debug for IdRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("IdRef").field(&self.id).finish()
}
}
impl<T> Clone for IdRef<T> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
model: self.model.clone(),
}
}
}
unsafe impl<T> Send for IdRef<T> {}
unsafe impl<T> Sync for IdRef<T> {}
impl<T> Display for IdRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.id)
}
}
impl<T: for<'a> Deserialize<'a>> From<String> for IdRef<T> {
fn from(value: String) -> Self {
IdRef::new(value)
}
}
impl<T: for<'a> Deserialize<'a>> From<&str> for IdRef<T> {
fn from(value: &str) -> Self {
IdRef::new(value.to_string())
}
}
impl<T: for<'a> Deserialize<'a>> From<Id> for IdRef<T> {
fn from(value: Id) -> Self {
IdRef::new(value.to_string())
}
}
impl<T: for<'a> Deserialize<'a>, X: for<'a> Deserialize<'a>> From<&IdRef<X>> for IdRef<T> {
fn from(value: &IdRef<X>) -> Self {
IdRef::new(value.id.clone())
}
}
impl<T: Identifiable + for<'a> Deserialize<'a>> From<&Model<T>> for IdRef<T> {
fn from(value: &Model<T>) -> Self {
IdRef::new(value.full_id())
}
}
impl<T: Identifiable + for<'a> Deserialize<'a>> From<&T> for IdRef<T> {
fn from(value: &T) -> Self {
IdRef::new(value.full_id())
}
}
impl<'de, T> Deserialize<'de> for IdRef<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(IdRef {
id: s,
model: std::marker::PhantomData,
})
}
}
impl<T> Serialize for IdRef<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.id.serialize(serializer)
}
}
impl<T: for<'a> Deserialize<'a> + Send + Sync + Identifiable + Serialize + 'static> IdRef<T> {
pub fn fetch_raw(&self, db: &Database) -> Model<T> {
let id = self.id.clone();
db.get::<T, _>(id).unwrap()
}
pub fn try_fetch(&self, db: &Database) -> Option<Model<T>> {
// todo : asset type
Some(self.fetch_raw(db))
}
}
impl<T: for<'a> Deserialize<'a>> IdRef<T> {
pub fn new(id: String) -> Self {
Self {
id,
model: std::marker::PhantomData,
}
}
pub unsafe fn fetch_raw_vfs(&self, vfs: &dyn vfs::FileSystem) -> T {
Store::get_id(&self.id, vfs).unwrap()
}
pub unsafe fn fetch_as<O: for<'a> Deserialize<'a>>(&self, vfs: &dyn vfs::FileSystem) -> O {
Store::get_id(&self.id, vfs).unwrap()
}
}
impl<T: Identifiable + Serialize + Send + Sync + 'static + for<'a> Deserialize<'a>> IdRef<T> {
/// Get the object this reference holds
pub unsafe fn get_raw(&self, vfs: &dyn vfs::FileSystem) -> Option<T> {
let split: Vec<&str> = self.id.split("::").collect();
if split.len() > 1 {
Store::get_id(&self.id, vfs)
} else {
Store::get(&T::model_id(), &self.id, vfs)
}
}
pub fn dereference(&self, db: &Database) -> Model<T> {
self.try_dereference(db).unwrap()
}
pub fn try_dereference(&self, db: &Database) -> Option<Model<T>> {
db.get(self.id.clone())
}
}
impl<
T: Identifiable + Saveable + Send + Sync + 'static,
S: Identifiable + Saveable + Send + Sync + 'static,
R: RelationRef,
U,
> Relation<T, S, R, U>
{
pub unsafe fn top(reference: &Model<R>, db: &Database) -> Model<T> {
db.get(reference.read().top().id.clone()).unwrap()
}
pub unsafe fn sub(reference: &Model<R>, db: &Database) -> Model<S> {
db.get(reference.read().sub().id.clone()).unwrap()
}
/// Parse a relation ref id into the respective pointer structure.
///
/// The ref id looks like: `[top_id->sub_id]`
pub fn parse_ref_id(id: &str) -> (String, String) {
// Expected format: "[top_id->sub_id]"
if let Some(stripped) = id.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
if let Some((top, sub)) = stripped.split_once("->") {
return (top.to_string(), sub.to_string());
}
}
panic!("Invalid ref_id format: {}", id);
}
/// Return a relation ref id for `top` and `sub`
pub fn ref_id(top: &Model<T>, sub: &Model<S>) -> String {
format!("[{}->{}]", top.reference(), sub.reference())
}
/// Return a collection id for this relation
pub fn collection_id(&self) -> String {
format!("{}-refs", self.name)
}
}
impl<
T: Identifiable + Saveable + Send + Sync + 'static,
S: Identifiable + Saveable + Send + Sync + 'static,
R: RelationRef,
> Relation<T, S, R, RelationKind::Unidirectional>
{
/// Get a `RelationReference` if a relation exists between `top` and `sub`
pub fn get(&self, top: &Model<T>, sub: &Model<S>, db: &Database) -> Option<Model<R>> {
let entry = db.get(Relation::<T, S, R, RelationKind::Unidirectional>::ref_id(
top, sub,
));
let entry_rev = db.get(Relation::<S, T, R, RelationKind::Unidirectional>::ref_id(
sub, top,
));
if let Some(entry) = entry {
return Some(entry);
}
if let Some(entry) = entry_rev {
return Some(entry);
}
None
}
/// Get all `RelationReference`s with `node`
pub fn get_all_with_t<X: Into<IdRef<T>>>(
&self,
node: X,
vfs: &dyn vfs::FileSystem,
) -> Vec<IdRef<R>> {
let ids = Store::get_ids(&self.collection_id(), vfs);
let node: IdRef<T> = node.into();
let top_id = node.id;
ids.into_iter()
.filter_map(|x| {
let (t_id, s_id) =
Relation::<T, S, R, RelationKind::Unidirectional>::parse_ref_id(&x);
if top_id == t_id || top_id == s_id {
return Some(IdRef::new(x.clone()));
} else {
return None;
}
})
.collect()
}
/// Get all `RelationReference`s with `node`
pub fn get_all_with_s<X: Into<IdRef<S>>>(
&self,
node: X,
vfs: &dyn vfs::FileSystem,
) -> Vec<IdRef<R>> {
let ids = Store::get_ids(&self.collection_id(), vfs);
let node: IdRef<S> = node.into();
let top_id = node.id;
ids.into_iter()
.filter_map(|x| {
let (t_id, s_id) =
Relation::<T, S, R, RelationKind::Unidirectional>::parse_ref_id(&x);
if top_id == t_id || top_id == s_id {
return Some(IdRef::new(x.clone()));
} else {
return None;
}
})
.collect()
}
/// Check if a relation exists between `top` and `sub`
pub fn exists<V: vfs::FileSystem>(&self, top: &Model<T>, sub: &Model<S>, vfs: &V) -> bool {
let entry: Option<RelationReference> = unsafe {
Store::get_opt(
&self.collection_id(),
&Relation::<T, S, R, RelationKind::Unidirectional>::ref_id(&top, &sub),
vfs,
)
};
let entry_rev: Option<RelationReference> = unsafe {
Store::get_opt(
&self.collection_id(),
&Relation::<S, T, R, RelationKind::Unidirectional>::ref_id(&sub, &top),
vfs,
)
};
if entry.is_some() || entry_rev.is_some() {
return true;
}
false
}
/// Add a relation for `top` and `sub` with optional `weight` and `meta` values
pub fn add(
&self,
top: &Model<T>,
sub: &Model<S>,
weight: Option<f64>,
meta: Option<serde_json::Value>,
db: &Database,
) {
let mut reference = self.get(&top, &sub, db).unwrap_or_else(|| {
Model::from_raw(R::from_ref(RelationReference {
top: IdRef::new(top.full_id()),
sub: IdRef::new(sub.full_id()),
uni: true,
weight: weight,
meta: None,
}))
});
reference.write(db, |r| {
r.update_meta(weight, meta.clone());
});
}
}
impl<
T: Identifiable + Saveable + Send + Sync + 'static,
S: Identifiable + Saveable + Send + Sync + 'static,
R: RelationRef,
> Relation<T, S, R, RelationKind::Bidirectional>
{
/// Get a `RelationReference` if a relation exists between `top` and `sub`
pub unsafe fn get(&self, top: &Model<T>, sub: &Model<S>, db: &Database) -> Option<Model<R>> {
let entry = db.get(Relation::<T, S, R, RelationKind::Bidirectional>::ref_id(
top, sub,
));
if let Some(entry) = entry {
return Some(entry);
}
None
}
/// Get all `RelationReference`s with `node` as top
pub fn get_all_top(&self, top: &Model<T>, vfs: &dyn vfs::FileSystem) -> Vec<IdRef<R>> {
let ids = Store::get_ids(&self.collection_id(), vfs);
let top_id = top.full_id();
ids.into_iter()
.filter_map(|x| {
let (t_id, _) = Relation::<T, S, R, RelationKind::Unidirectional>::parse_ref_id(&x);
if top_id == t_id {
return Some(IdRef::new(format!("{}::{x}", R::model_id())));
} else {
return None;
}
})
.collect()
}
/// Get all `RelationReference`s with `node` as sub
pub fn get_all_sub(&self, sub: &Model<T>, vfs: &dyn vfs::FileSystem) -> Vec<IdRef<R>> {
let ids = Store::get_ids(&self.collection_id(), vfs);
let sub_id = sub.full_id();
ids.into_iter()
.filter_map(|x| {
let (_, s_id) = Relation::<T, S, R, RelationKind::Unidirectional>::parse_ref_id(&x);
if sub_id == s_id {
return Some(IdRef::new(x));
} else {
return None;
}
})
.collect()
}
/// Check if a relation exists between `top` and `sub`
pub fn exists(&self, top: &Model<T>, sub: &Model<S>, vfs: &dyn vfs::FileSystem) -> bool {
let entry: Option<RelationReference> = unsafe {
Store::get_opt(
&self.collection_id(),
&Relation::<T, S, R, RelationKind::Bidirectional>::ref_id(&top, &sub),
vfs,
)
};
if entry.is_some() {
return true;
}
false
}
/// Add a relation for `top` and `sub` with optional `weight` and `meta` values
pub fn add(
&self,
top: &Model<T>,
sub: &Model<S>,
weight: Option<f64>,
meta: Option<serde_json::Value>,
db: &Database,
) {
let mut reference = unsafe {
self.get(&top, &sub, db).unwrap_or_else(|| {
Model::from_raw(R::from_ref(RelationReference {
top: IdRef::new(top.full_id()),
sub: IdRef::new(sub.full_id()),
uni: true,
weight: weight,
meta: None,
}))
})
};
reference.write(db, |reference: &mut _| {
reference.update_meta(weight, meta.clone());
});
}
}
pub trait RelationRef:
Identifiable + for<'a> Deserialize<'a> + Serialize + Send + Sync + 'static
{
fn top(&self) -> IdRef<serde_json::Value>;
fn sub(&self) -> IdRef<serde_json::Value>;
fn from_ref(r: RelationReference) -> Self;
fn update_meta(&mut self, weight: Option<f64>, meta: Option<serde_json::Value>);
}
#[derive(Deserialize, Serialize, Debug)]
pub struct RelationReference {
pub top: IdRef<serde_json::Value>,
pub sub: IdRef<serde_json::Value>,
pub uni: bool,
pub weight: Option<f64>,
pub meta: Option<serde_json::Value>,
}
impl RelationReference {
pub fn alter_meta<F: Fn(&mut X), X: for<'a> Deserialize<'a> + Default + Serialize>(
&mut self,
u: F,
) {
let mut meta =
serde_json::from_value(self.meta.clone().unwrap()).unwrap_or_else(|_| X::default());
u(&mut meta);
self.meta = Some(serde_json::to_value(meta).unwrap());
}
pub fn id(&self) -> String {
format!("[{}->{}]", self.top.id, self.sub.id)
}
/*
pub unsafe fn save(&self, vfs: &dyn vfs::FileSystem) {
Store::save_raw(&self.model_id_real(), &self.id(), self, false, vfs);
}
*/
}
/// Dereference a `Vec<_>` with `IdRef<T>`s into their objects
pub fn dereference<
T: for<'a> Deserialize<'a> + Serialize + Identifiable + Send + Sync + 'static,
>(
v: &[IdRef<T>],
db: &Database,
) -> Vec<Model<T>> {
v.into_iter().map(|x| x.fetch_raw(db)).collect()
}
pub unsafe fn dereference_raw<T: for<'a> Deserialize<'a>>(v: &[IdRef<T>], db: &Database) -> Vec<T> {
v.into_iter()
.map(|x| unsafe { x.fetch_raw_vfs(&*db.storage) })
.collect()
}
// TODO : generally clean up traversal loops
pub fn clean_graph_traversal<T>(id: &str, route: &[IdRef<T>]) -> Vec<IdRef<T>> {
let mut index = 0;
for (i, node) in route.iter().enumerate() {
let node = node.id.to_string();
if node == id {
index = i + 1;
}
}
route[index..].to_vec()
}
/// Find a path from `from` to `to` with max `reach` steps to get there.
///
/// `fetch_relation` is a function for traversing the graph ( `Fn(IdRef<T>, &Database) -> Vec<IdRef<T>>` )
pub fn find_path<F, T: Debug>(
from: IdRef<T>,
to: IdRef<T>,
reach: u32,
fetch_relation: F,
db: &Database,
) -> Vec<IdRef<T>>
where
F: Fn(&IdRef<T>, &Database) -> Vec<IdRef<T>> + Sync + Send + Clone,
{
log::info!("path from {from} r {reach}");
let my_friends = fetch_relation(&from, db);
log::trace!("have children {my_friends:?}");
if reach == 0 {
log::trace!("no reach :(");
return vec![];
}
my_friends
.par_iter()
.filter_map(|friend| {
if friend.id == to.id {
log::trace!("found you {friend}");
return Some(vec![friend.clone()]);
}
log::trace!("nooo... looking deeper");
let p = find_path(
friend.clone(),
to.clone(),
reach - 1,
fetch_relation.clone(),
db,
);
if !p.is_empty() {
let mut ret = vec![friend.clone()];
ret.extend(p.iter().cloned());
return Some(ret);
}
None
})
.find_any(|_| true) // find any successful path in parallel
.unwrap_or_else(|| {
log::trace!("END SEARCH");
vec![]
})
}
/// Get the other which isnt oneself from unidirectional `RelationReference`s
pub fn get_other<
X: Identifiable + Send + Sync + Serialize + for<'a> Deserialize<'a>,
T: Into<IdRef<X>>,
R: RelationRef,
>(
id: T,
refs: Vec<IdRef<R>>,
db: &Database,
) -> Vec<IdRef<serde_json::Value>> {
let mut other = Vec::new();
let id: IdRef<X> = id.into();
let id = id.id;
for r in refs {
if let Some(r) = r.try_dereference(&db) {
if r.read().top().id != id {
other.push(r.read().top());
} else {
other.push(r.read().sub());
}
} else {
log::error!("Could not get Model<{}> from {}", R::model_id(), r.id);
}
}
other
}

118
src/db/store.rs Normal file
View file

@ -0,0 +1,118 @@
use serde::{Deserialize, Serialize};
use crate::Identifiable;
fn read_to_vec(path: &str, vfs: &dyn vfs::FileSystem) -> Option<Vec<u8>> {
let mut f = vfs.open_file(path).ok()?;
let mut content = Vec::new();
f.read_to_end(&mut content).ok()?;
Some(content)
}
pub fn write(path: &str, data: &[u8], vfs: &dyn vfs::FileSystem) {
let mut f = vfs.create_file(path).unwrap();
f.write_all(&data).unwrap();
}
pub struct Store {}
impl Store {
pub unsafe fn get<T: for<'a> Deserialize<'a>>(
collection: &str,
id: &str,
vfs: &dyn vfs::FileSystem,
) -> Option<T> {
let path = std::path::Path::new(&format!("/collection/{collection}"))
.join(id)
.display()
.to_string();
log::info!("IO READ {path}");
let content = read_to_vec(&path, vfs)?;
rmp_serde::from_slice(&content).ok()
}
pub unsafe fn get_opt<T: for<'a> Deserialize<'a>>(
collection: &str,
id: &str,
vfs: &dyn vfs::FileSystem,
) -> Option<T> {
let path = std::path::Path::new(&format!("/collection/{collection}"))
.join(id)
.display()
.to_string();
rmp_serde::from_slice(&read_to_vec(&path, vfs)?).unwrap()
}
pub unsafe fn save_raw<T: Serialize>(
collection: &str,
id: &str,
data: &T,
named: bool,
vfs: &dyn vfs::FileSystem,
) {
let c = if named {
rmp_serde::to_vec_named(data).unwrap()
} else {
rmp_serde::to_vec(data).unwrap()
};
let path = std::path::Path::new(&format!("/collection/{collection}"))
.join(id)
.display()
.to_string();
let _ = vfs.create_dir(&format!("/collection/{collection}"));
log::info!("IO WRITE {path}");
write(&path, &c, vfs);
}
pub unsafe fn save<T: Serialize + Identifiable>(
collection: &str,
data: &T,
named: bool,
vfs: &dyn vfs::FileSystem,
) {
Self::save_raw(collection, &data.id().to_string(), &data, named, vfs);
}
pub unsafe fn get_id<T: for<'a> Deserialize<'a>>(
id: &str,
vfs: &dyn vfs::FileSystem,
) -> Option<T> {
let split_d: Vec<_> = id.split("[").collect();
let id = split_d.first()?;
let mut split: Vec<_> = id.split("::").collect();
let sid: &str = split.pop()?;
let collection = split.join("::");
let id = format!(
"{}{sid}{}",
if split_d.len() > 1 { "[" } else { "" },
split_d
.iter()
.skip(1)
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join("[")
);
Store::get(&collection, &id, vfs)
}
pub fn get_ids(collection: &str, vfs: &dyn vfs::FileSystem) -> Vec<String> {
vfs.read_dir(&format!("/collection/{collection}"))
.unwrap()
.collect()
}
pub fn list(vfs: &dyn vfs::FileSystem) -> Vec<String> {
vfs.read_dir("/collection").unwrap().collect()
}
}
pub trait Saveable: Identifiable + for<'a> Deserialize<'a> + Serialize {
unsafe fn save(&self, named: bool, vfs: &dyn vfs::FileSystem) {
Store::save(&Self::model_id(), self, named, vfs);
}
unsafe fn get(id: &str, vfs: &dyn vfs::FileSystem) -> Option<Self> {
Store::get(&Self::model_id(), id, vfs)
}
}

110
src/lib.rs Normal file
View file

@ -0,0 +1,110 @@
//mod model;
use db::id::Id;
pub mod db;
use once_cell::sync::OnceCell;
pub use owl_macro::model;
pub use owl_macro::relation;
pub use serde::{Deserialize, Serialize};
pub static DB: OnceCell<db::Database> = OnceCell::new();
/// Set a `Database` as global.
///
/// All global macro actions are then done against that `Database`.
#[macro_export]
macro_rules! set_global_db {
($db:ident) => {
assert!($crate::DB.set($db).is_ok());
};
}
#[macro_export]
macro_rules! dereference {
($id:ident) => {
$id.dereference(&$crate::DB.get().unwrap())
};
($id:expr) => {
$id.dereference(&$crate::DB.get().unwrap())
};
}
#[macro_export]
macro_rules! save {
($model:ident) => {
$crate::DB.get().unwrap().save($model)
};
($model:expr) => {
$crate::DB.get().unwrap().save($model)
};
}
#[macro_export]
macro_rules! get {
($id:ident) => {
$crate::DB.get().unwrap().get($id)
};
($id:expr) => {
$crate::DB.get().unwrap().get($id)
};
}
#[macro_export]
macro_rules! update {
($ids:ident, $u:expr) => {
$crate::DB.get().unwrap().update($ids, $u)
};
($ids:expr, $u:expr) => {
$crate::DB.get().unwrap().update($ids, $u)
};
}
#[macro_export]
macro_rules! query {
($id:ident) => {
$crate::DB.get().unwrap().query($id)
};
($id:expr) => {
$crate::DB.get().unwrap().query($id)
};
}
pub mod prelude {
pub use super::db::field::Historic;
pub use super::db::id::Id;
pub use super::db::relation::{dereference, IdRef, Relation, RelationKind};
pub use super::db::store::{Saveable, Store};
pub use super::db::Database;
pub use super::db::Model;
pub use super::Identifiable;
pub use super::{model, relation};
}
// TODO : wire format for endpoints
// TODO : auth + traceability
// TODO : deletion?
// TODO : make relations able to be inactive/active
pub trait Identifiable: Sized + for<'a> Deserialize<'a> {
fn id(&self) -> Id;
/// Get a typed `IdRef<T>` for this ID
fn reference(&self) -> crate::db::relation::IdRef<Self> {
crate::db::relation::IdRef::new(self.full_id())
}
fn model_id() -> String;
fn full_id(&self) -> String {
format!("{}::{}", Self::model_id(), self.id())
}
}
impl Identifiable for serde_json::Value {
fn id(&self) -> Id {
serde_json::from_value(self.as_object().unwrap().get("id").unwrap().clone()).unwrap()
}
fn model_id() -> String {
"json".to_string()
}
}

128
src/main.rs Normal file
View file

@ -0,0 +1,128 @@
use std::io::Read;
use owl::prelude::*;
// data points
/*
// events
struct Event {
pub ts: String,
pub content: String,
pub references: Vec<Reference>,
pub attachments: Vec<File>
}
pub struct File(String);
*/
// TODO : document
// TODO : serve api
// TODO : schemaless data storage (named)
// TODO : export/import?
// pub static PG: OnceCell<sqlx::PgPool> = OnceCell::const_new();
#[macro_export]
macro_rules! get_pg {
() => {
if let Some(client) = $crate::PG.get() {
client
} else {
let client = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&std::env::var("DATABASE_URL").unwrap())
.await
.unwrap();
$crate::PG.set(client).unwrap();
$crate::PG.get().unwrap()
}
};
}
/*
#[rocket::main]
async fn main() {
let cors = rocket_cors::CorsOptions {
allowed_origins: rocket_cors::AllowedOrigins::all(),
allowed_methods: vec![Method::Get, Method::Post, Method::Options]
.into_iter()
.map(From::from)
.collect(),
allowed_headers: rocket_cors::AllowedHeaders::all(),
allow_credentials: true,
..Default::default()
}
.to_cors()
.expect("error creating CORS options");
rocket::build()
.mount(
"/",
route![
routes::model_api_create,
routes::model_api_info,
routes::model_api_update,
routes::model_overview
],
)
.attach(cors)
//.manage(pg)
.launch()
.await
.unwrap();
}
*/
mod cli;
pub fn main() {
let cli: cli::OwlCLI = argh::from_env();
match cli.nested {
cli::OwlCLICommands::List(list_command) => {
let db_path = list_command.db.unwrap_or("./db".to_string());
let db = Database::filesystem(&db_path);
if let Some(collection) = list_command.collection {
let entries = db.list_entries(&collection);
println!("List {db_path} [{collection}]:");
for t in entries {
println!(" - {t}");
}
} else {
let collections = db.list();
println!("List {db_path}:");
for c in collections {
println!(" - {c}");
}
}
}
cli::OwlCLICommands::Get(get_command) => {
let db_path = get_command.db.unwrap_or("./db".to_string());
let db = Database::filesystem(&db_path);
let x: rmpv::Value = db.get_id(&get_command.collection, &get_command.id);
println!("{}", serde_json::to_string(&x).unwrap());
}
cli::OwlCLICommands::Store(store_command) => {
let db_path = store_command.db.unwrap_or("./db".to_string());
let db = Database::filesystem(&db_path).named();
let mut buffer = String::new();
std::io::stdin()
.read_to_string(&mut buffer)
.expect("Failed to read from stdin");
let json_value: serde_json::Value =
serde_json::from_str(&buffer).expect("Failed to parse JSON");
let id = json_value.id();
unsafe {
db.save_raw(&json_value, &store_command.collection);
}
println!("{}::{id}", store_command.collection);
}
}
}

24
tests/basic.rs Normal file
View file

@ -0,0 +1,24 @@
use owl::prelude::*;
#[cfg(test)]
#[model]
pub struct TestModel {
pub id: Id,
pub data: String,
}
#[test]
fn save_load_db() {
let data = "TestData".to_string();
let db = Database::in_memory();
let m = TestModel {
id: Id::String(data.clone()),
data: data.clone(),
};
db.save(m);
let get_model: Model<TestModel> = db.get(data.as_str()).unwrap();
assert_eq!(get_model.read().id.to_string(), data);
}