Initial commit
+ Pulled out database logic from Geoffrey-RS + Added import/export feature from json + Updated tests + Clippy + fmtmain
commit
fb990ce233
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/test_database
|
||||
/.idea
|
|
@ -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"
|
|
@ -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"
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue