Initial commit

+ Pulled out database logic from Geoffrey-RS
+ Added import/export feature from json
+ Updated tests
+ Clippy + fmt
main
Joey Hines 2023-01-15 18:55:55 -07:00
commit fb990ce233
No known key found for this signature in database
GPG Key ID: 995E531F7A569DDB
10 changed files with 965 additions and 0 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
/target
/test_database
/.idea

340
Cargo.lock generated 100644
View File

@ -0,0 +1,340 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "j_db"
version = "0.1.0"
dependencies = [
"byteorder",
"json",
"lazy_static",
"log",
"regex",
"serde",
"serde_json",
"sled",
]
[[package]]
name = "json"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

19
Cargo.toml 100644
View File

@ -0,0 +1,19 @@
[package]
name = "j_db"
version = "0.1.0"
authors = ["Joey Hines <joey@ahines.net>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sled = "0.34.7"
serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"
byteorder = "1.4.2"
log = "0.4.17"
json = "0.12.4"
regex = "1.5.6"
[dev-dependencies]
lazy_static = "1.4.0"

373
src/database.rs 100644
View File

@ -0,0 +1,373 @@
use crate::error::{JDbError, Result};
use crate::metadata::DBMetadata;
use crate::model::JdbModel;
use crate::query::QueryBuilder;
use json::JsonValue;
use sled::IVec;
use std::convert::TryInto;
use std::path::Path;
pub const DB_METADATA_ID: u64 = 1;
fn option_bytes_to_model<T: JdbModel>(bytes: Option<IVec>, id: u64) -> Result<T> {
if let Some(bytes) = bytes {
Ok(T::try_from_bytes(&bytes)?)
} else {
log::debug!("{} of id {} was not found in the database", T::tree(), id);
Err(JDbError::NotFound)
}
}
pub struct Database {
pub(crate) db: sled::Db,
}
impl Database {
pub fn new(db_path: &Path) -> Result<Self> {
let db = sled::open(db_path)?;
let db = Self { db };
let version = match db.version() {
Ok(version) => version,
Err(_) => {
let db_metadata = DBMetadata {
id: Some(DB_METADATA_ID),
version: 0,
};
db.insert(db_metadata)?.version
}
};
log::info!("jDb Version V{}", version);
Ok(db)
}
fn get_tree<T>(&self) -> Result<sled::Tree>
where
T: JdbModel,
{
Ok(self.db.open_tree::<String>(T::tree())?)
}
pub fn insert<T>(&self, mut model: T) -> Result<T>
where
T: JdbModel,
{
let id = match model.id() {
Some(id) => id,
None => {
let id = self.db.generate_id()?;
model.set_id(id);
id
}
};
let match_count = self
.filter(|o_id, o: &T| o_id != id && !o.check_unique(&model))?
.count();
if match_count > 0 {
log::debug!("{} is not unique: {:?}", T::tree(), model);
return Err(JDbError::NotUnique);
}
let tree = self.get_tree::<T>()?;
let id_bytes = id.to_be_bytes();
tree.insert(id_bytes, model.to_bytes()?)?;
Ok(model)
}
pub fn get<T>(&self, id: u64) -> Result<T>
where
T: JdbModel,
{
let tree = self.get_tree::<T>()?;
option_bytes_to_model(tree.get(id.to_be_bytes())?, id)
}
pub fn clear_tree<T>(&self) -> Result<()>
where
T: JdbModel,
{
self.db.drop_tree(T::tree())?;
Ok(())
}
pub fn filter<'a, T>(
&self,
f: impl Fn(u64, &T) -> bool + 'a,
) -> Result<impl Iterator<Item = T> + 'a>
where
T: JdbModel,
{
let tree = self.db.open_tree(T::tree())?;
Ok(tree.iter().filter_map(move |e| {
if let Ok((id, data)) = e {
let id = u64::from_be_bytes(id.to_vec().try_into().unwrap());
let data = match T::try_from_bytes(&data) {
Ok(data) => data,
Err(err) => {
log::debug!(
"Invalid data: {}",
String::from_utf8(data.to_vec()).unwrap_or_default()
);
panic!("Unable to parse {} model from bytes: {}", T::tree(), err);
}
};
if f(id, &data) {
Some(data)
} else {
None
}
} else {
None
}
}))
}
pub fn run_query<T>(&self, query_builder: QueryBuilder<T>) -> Result<Vec<T>>
where
T: JdbModel,
{
let result: Vec<T> = self
.filter(|id, loc: &T| {
for query in &query_builder.queries {
let res = query(id, loc);
if !res {
return false;
}
}
true
})?
.collect();
if result.is_empty() {
Err(JDbError::NotFound)
} else {
Ok(result)
}
}
pub fn remove<T>(&self, id: u64) -> Result<T>
where
T: JdbModel,
{
let tree = self.db.open_tree(T::tree())?;
option_bytes_to_model(tree.remove(id.to_be_bytes())?, id)
}
pub fn tree_iter<T>(&self) -> Result<sled::Iter>
where
T: JdbModel,
{
Ok(self.db.open_tree(T::tree()).map(|tree| tree.iter())?)
}
pub fn version(&self) -> Result<u64> {
Ok(self.get::<DBMetadata>(DB_METADATA_ID)?.version)
}
pub(crate) fn set_version(&mut self, version: u64) -> Result<()> {
let mut md = self.get::<DBMetadata>(DB_METADATA_ID)?;
md.version = version;
self.insert(md)?;
Ok(())
}
pub fn dump_db(&self) -> Result<JsonValue> {
let mut json = JsonValue::new_object();
let mut global_array = JsonValue::new_array();
for model in self.db.iter() {
let (_, model) = model?;
let model_str = String::from_utf8(model.to_vec()).unwrap();
let model_json = json::parse(&model_str)?;
global_array.push(json::from(model_json))?;
}
json.insert("global", global_array)?;
for tree in self.db.tree_names() {
let tree = self.db.open_tree(tree)?;
let mut tree_array = JsonValue::new_array();
for model in tree.iter() {
let (_, model) = model?;
let model_str = String::from_utf8(model.to_vec()).unwrap();
let model_json = json::parse(&model_str)?;
tree_array.push(json::from(model_json))?;
}
let tree_name = String::from_utf8(tree.name().to_vec()).unwrap();
json.insert(&tree_name, tree_array)?;
}
Ok(json)
}
pub fn import_db(&self, json: JsonValue) -> Result<()> {
for model in json["global"].members() {
let id_bytes = model["id"].as_u64().unwrap().to_be_bytes();
self.db.insert(id_bytes, model.to_string().as_bytes())?;
}
for (tree, models) in json.entries() {
for model in models.members() {
let id_bytes = model["id"].as_u64().unwrap().to_be_bytes();
if tree == "global" {
self.db.insert(id_bytes, model.to_string().as_bytes())?;
} else {
let tree = self.db.open_tree(tree)?;
tree.insert(id_bytes, model.to_string().as_bytes())?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::model::JdbModel;
use crate::test::{cleanup, User, DB, LOCK};
use std::time::Instant;
#[test]
fn test_insert() {
let _lock = LOCK.lock().unwrap();
cleanup();
let user = User::new("Test", 1);
let user2 = DB.insert::<User>(user.clone()).unwrap();
assert!(user2.id().is_some());
assert_eq!(user.name, user2.name);
cleanup();
}
#[test]
fn test_unique_insert() {
let _lock = LOCK.lock().unwrap();
cleanup();
let user1 = User::new("Test", 1);
let user2 = User::new("Test", 1);
DB.insert::<User>(user1.clone()).unwrap();
assert_eq!(DB.insert::<User>(user2.clone()).is_err(), true);
cleanup();
}
#[test]
fn test_get() {
let _lock = LOCK.lock().unwrap();
cleanup();
let user1 = User::new("Test", 1);
let user1_db = DB.insert::<User>(user1.clone()).unwrap();
let user1_get = DB.get::<User>(user1_db.id().unwrap()).unwrap();
assert_eq!(user1_get.name, user1.name);
cleanup();
}
#[test]
fn test_filter() {
let _lock = LOCK.lock().unwrap();
cleanup();
let user = User::new("Test", 1);
let user = DB.insert::<User>(user.clone()).unwrap();
let count = DB
.filter(|id: u64, u: &User| {
assert_eq!(id, user.id().unwrap());
u.id().unwrap() == user.id().unwrap()
})
.unwrap()
.count();
assert_eq!(count, 1);
cleanup();
}
#[test]
fn test_remove() {
let _lock = LOCK.lock().unwrap();
cleanup();
let user = User::new("CoolZero123", 1);
let user_insert = DB.insert::<User>(user.clone()).unwrap();
let user_remove = DB.remove::<User>(user_insert.id().unwrap()).unwrap();
assert!(DB.get::<User>(user_insert.id().unwrap()).is_err());
assert_eq!(user_remove.id().unwrap(), user_insert.id().unwrap());
cleanup();
}
#[test]
fn test_speed() {
let _lock = LOCK.lock().unwrap();
cleanup();
let insert_count = 1000;
let timer = Instant::now();
for i in 0..insert_count {
let user = User::new(&format!("User{}", i), 0);
DB.insert::<User>(user).unwrap();
}
DB.db.flush().unwrap();
let sec_elapsed = timer.elapsed().as_secs_f32();
println!(
"Completed in {}s. {} inserts per second",
sec_elapsed,
insert_count as f32 / sec_elapsed
);
cleanup()
}
#[test]
fn test_dump_and_load() {
let _lock = LOCK.lock().unwrap();
cleanup();
let mut users = vec![];
for i in 0..10 {
let user = User::new(&format!("User{}", i), 0);
let u = DB.insert::<User>(user).unwrap();
users.push(u);
}
let out = DB.dump_db().unwrap();
println!("{}", out);
DB.import_db(out).unwrap();
for user in users {
let import_user = DB.get::<User>(user.id().unwrap()).unwrap();
assert_eq!(user.name, import_user.name);
}
cleanup();
}
}

50
src/error.rs 100644
View File

@ -0,0 +1,50 @@
pub type Result<T> = std::result::Result<T, JDbError>;
#[derive(Debug)]
pub enum JDbError {
SledError(sled::Error),
SerdeJsonError(serde_json::Error),
NotUnique,
NotFound,
RegexError(regex::Error),
JsonError(json::JsonError),
}
impl std::error::Error for JDbError {}
impl std::fmt::Display for JDbError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JDbError::SledError(e) => write!(f, "Sled Error: {}", e),
JDbError::SerdeJsonError(e) => write!(f, "Serde JSON Error: {}", e),
JDbError::NotUnique => write!(f, "Entry is not unique."),
JDbError::NotFound => write!(f, "Entry was not found."),
JDbError::RegexError(e) => write!(f, "Regex Error: {}", e),
JDbError::JsonError(e) => write!(f, "JSON Error: {}", e),
}
}
}
impl From<sled::Error> for JDbError {
fn from(e: sled::Error) -> Self {
JDbError::SledError(e)
}
}
impl From<serde_json::Error> for JDbError {
fn from(e: serde_json::Error) -> Self {
JDbError::SerdeJsonError(e)
}
}
impl From<regex::Error> for JDbError {
fn from(e: regex::Error) -> Self {
Self::RegexError(e)
}
}
impl From<json::Error> for JDbError {
fn from(e: json::Error) -> Self {
Self::JsonError(e)
}
}

66
src/lib.rs 100644
View File

@ -0,0 +1,66 @@
#![allow(dead_code)]
extern crate core;
pub mod database;
pub mod error;
pub mod metadata;
pub mod migration;
pub mod model;
pub mod query;
#[cfg(test)]
mod test {
use crate::database::Database;
use crate::model::JdbModel;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Mutex;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct User {
id: Option<u64>,
pub name: String,
pub age: u16,
}
impl User {
pub fn new(name: &str, age: u16) -> Self {
Self {
id: None,
name: name.to_string(),
age,
}
}
}
impl JdbModel for User {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"User".to_string()
}
fn check_unique(&self, other: &Self) -> bool {
self.name != other.name
}
}
lazy_static! {
pub static ref DB: Database = Database::new(Path::new("test_database")).unwrap();
pub static ref LOCK: Mutex<()> = Mutex::default();
}
pub fn cleanup() {
DB.clear_tree::<User>().unwrap();
DB.db.clear().unwrap();
DB.db.flush().unwrap();
}
}

23
src/metadata.rs 100644
View File

@ -0,0 +1,23 @@
use crate::database::DB_METADATA_ID;
use crate::model::JdbModel;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct DBMetadata {
pub id: Option<u64>,
pub version: u64,
}
impl JdbModel for DBMetadata {
fn id(&self) -> Option<u64> {
Some(DB_METADATA_ID)
}
fn set_id(&mut self, _id: u64) {
self.id = Some(DB_METADATA_ID)
}
fn tree() -> String {
"DBMetadata".to_string()
}
}

View File

@ -0,0 +1,32 @@
use crate::database::Database;
use crate::error::Result;
use crate::metadata::DBMetadata;
pub trait Migration {
fn up(&self, db: &Database) -> Result<()>;
fn down(&self, db: &Database) -> Result<()>;
fn version(&self) -> u64;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Direction {
Up,
Down,
}
pub fn do_migration<T: Migration>(db: &Database, migration: T, direction: Direction) -> Result<()> {
if direction == Direction::Up {
migration.up(db)?;
} else {
migration.down(db)?;
}
let metadata = DBMetadata {
id: None,
version: migration.version(),
};
db.insert(metadata)?;
Ok(())
}

29
src/model.rs 100644
View File

@ -0,0 +1,29 @@
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::fmt::Debug;
pub trait JdbModel: Serialize + DeserializeOwned + Debug {
fn id(&self) -> Option<u64>;
fn set_id(&mut self, id: u64);
fn tree() -> String;
fn check_unique(&self, _: &Self) -> bool {
true
}
fn to_json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn try_from_str(s: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(s)
}
fn to_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(self)
}
fn try_from_bytes(b: &[u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(b)
}
}

30
src/query/mod.rs 100644
View File

@ -0,0 +1,30 @@
use crate::model::JdbModel;
pub type GeoffreyDBQuery<T> = Box<dyn Fn(u64, &T) -> bool>;
pub struct QueryBuilder<T: JdbModel> {
pub queries: Vec<GeoffreyDBQuery<T>>,
}
impl<T: JdbModel> Default for QueryBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: JdbModel> QueryBuilder<T> {
pub fn new() -> Self {
QueryBuilder {
queries: Vec::new(),
}
}
pub fn add_query_clause(mut self, clause: GeoffreyDBQuery<T>) -> Self {
self.queries.push(clause);
self
}
pub fn with_id(self, id: u64) -> Self {
self.add_query_clause(Box::new(move |entry_id, _| entry_id == id))
}
}