add a way to list paths
This commit is contained in:
parent
f777ed1a4f
commit
010c2492e2
8 changed files with 115 additions and 23 deletions
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "microdb"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
description = "A very small in-program database with cache, disk storage, etc."
|
||||
license = "MIT"
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ fn main() {
|
|||
"example_db.data.mdb",
|
||||
"example_db.meta.mdb",
|
||||
dbg!(MicroDB::sensible_cache_period(10.0, 0.01, 0.1, 1.0)),
|
||||
200,
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
println!("\nSetting test --raw--> vec![true; 500]");
|
||||
|
|
@ -124,15 +124,15 @@ fn main() {
|
|||
);
|
||||
db.remove("test").unwrap();
|
||||
|
||||
println!("\nSetting horizontal_test/{{0..50000}} --raw--> true");
|
||||
println!("\nSetting horizontal_test/{{0..10000}} --raw--> true");
|
||||
let wtime = SystemTime::now();
|
||||
for i in 0..50000_u32 {
|
||||
for i in 0..10000_u32 {
|
||||
db.set_raw("horizontal_test".sub_path(i), true).unwrap()
|
||||
}
|
||||
let welapsed = wtime.elapsed().unwrap().as_millis();
|
||||
println!("Reading back all values...");
|
||||
let rtime = SystemTime::now();
|
||||
for i in 0..50000_u32 {
|
||||
for i in 0..10000_u32 {
|
||||
assert_eq!(
|
||||
black_box::<bool>(db.get_raw("horizontal_test".sub_path(i)).unwrap().unwrap()),
|
||||
true
|
||||
|
|
@ -142,15 +142,15 @@ fn main() {
|
|||
println!(
|
||||
"Done! Write took {}ms: {}ms per write; Read took {}ms: {}ms per read.",
|
||||
welapsed,
|
||||
welapsed as f64 / 50000.0,
|
||||
welapsed as f64 / 10000.0,
|
||||
relapsed,
|
||||
relapsed as f64 / 50000.0,
|
||||
relapsed as f64 / 10000.0,
|
||||
);
|
||||
|
||||
println!("\n\n-- benchmarks done --\n\n");
|
||||
|
||||
// there is a slash and stuff in this username!
|
||||
let username = "TudbuT \\/\\ Daniella".to_owned();
|
||||
let username = "TudbuT \\/\\ Daniel".to_owned();
|
||||
println!(
|
||||
"Escaped username: {}",
|
||||
Escape(username.clone()).to_db_path()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ where
|
|||
|
||||
fn remove<P: Path>(path: P, db: &MicroDB) -> Result<(), std::io::Error> {
|
||||
db.remove_raw(path.sub_path("type"))?;
|
||||
db.remove_raw(path.sub_path("data"))?;
|
||||
db.remove(path.sub_path("data"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -73,4 +73,12 @@ where
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn paths<P: Path>(path: P, db: &MicroDB) -> Result<Vec<String>, std::io::Error> {
|
||||
if matches!(db.get_raw(path.sub_path("type"))?, Some(x) if x) {
|
||||
Ok(vec![path.sub_path("type"), path.sub_path("data")])
|
||||
} else {
|
||||
Ok(vec![path.sub_path("type")])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::io;
|
||||
|
||||
use crate::MicroDB;
|
||||
|
||||
use super::{ComObj, Path, RawObj};
|
||||
|
|
@ -43,7 +45,7 @@ where
|
|||
T: ComObj,
|
||||
E: ComObj,
|
||||
{
|
||||
fn to_db<P: Path>(self, path: P, db: &MicroDB) -> Result<(), std::io::Error> {
|
||||
fn to_db<P: Path>(self, path: P, db: &MicroDB) -> Result<(), io::Error> {
|
||||
db.set_raw(path.sub_path("type"), self.is_ok())?;
|
||||
match self {
|
||||
Ok(x) => db.set_com(path.sub_path("data"), x),
|
||||
|
|
@ -51,13 +53,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn remove<P: Path>(path: P, db: &MicroDB) -> Result<(), std::io::Error> {
|
||||
fn remove<P: Path>(path: P, db: &MicroDB) -> Result<(), io::Error> {
|
||||
db.remove_raw(path.sub_path("type"))?;
|
||||
db.remove_raw(path.sub_path("data"))?;
|
||||
db.remove(path.sub_path("data"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_db<P: Path>(path: P, db: &MicroDB) -> Result<Option<Self>, std::io::Error> {
|
||||
fn from_db<P: Path>(path: P, db: &MicroDB) -> Result<Option<Self>, io::Error> {
|
||||
if let Some(x) = db.get_raw(path.sub_path("type"))? {
|
||||
if x {
|
||||
if let Some(data) = db.get_com(path.sub_path("data"))? {
|
||||
|
|
@ -79,4 +81,8 @@ where
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn paths<P: Path>(path: P, db: &MicroDB) -> Result<Vec<String>, io::Error> {
|
||||
Ok(vec![path.sub_path("type"), path.sub_path("data")])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ pub trait ComObj: Sized {
|
|||
/// Turns the DB object back into the original object. None means failure to decode.
|
||||
/// Don't forget to always use [`Path::sub_path`].
|
||||
fn from_db<P: Path>(path: P, db: &MicroDB) -> Result<Option<Self>, io::Error>;
|
||||
/// Returns list of paths in the object. If not implemented manually, uses catch-all
|
||||
/// function [`MicroDB::get_paths`].
|
||||
fn paths<P: Path>(path: P, db: &MicroDB) -> Result<Vec<String>, io::Error> {
|
||||
db.get_paths(Some(path))
|
||||
}
|
||||
}
|
||||
|
||||
/// When implemented, automatically implements ComObj for the RawObj.
|
||||
|
|
@ -73,6 +78,10 @@ where
|
|||
fn from_db<P: Path>(path: P, db: &MicroDB) -> Result<Option<Self>, io::Error> {
|
||||
db.get_raw(path)
|
||||
}
|
||||
|
||||
fn paths<P: Path>(_path: P, _db: &MicroDB) -> Result<Vec<String>, io::Error> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Automatically implements ComObj for a RawObj
|
||||
|
|
|
|||
|
|
@ -52,8 +52,9 @@ where
|
|||
|
||||
fn remove<P: Path>(path: P, db: &MicroDB) -> Result<(), std::io::Error> {
|
||||
let Some(len): Option<u64> = db.get_raw(path.clone())? else { return Ok(()) };
|
||||
db.remove_raw(path.clone())?;
|
||||
for i in 0..len {
|
||||
db.remove_raw(path.sub_path(i))?;
|
||||
db.remove(path.sub_path(i))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -67,4 +68,9 @@ where
|
|||
}
|
||||
Ok(Some(v))
|
||||
}
|
||||
|
||||
fn paths<P: Path>(path: P, db: &MicroDB) -> Result<Vec<String>, std::io::Error> {
|
||||
let Some(len): Option<u64> = db.get_raw(path.clone())? else { return Ok(vec![]) };
|
||||
Ok((0..len as u64).map(|x| path.sub_path(x)).collect())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/db.rs
20
src/db.rs
|
|
@ -65,6 +65,26 @@ impl MicroDB {
|
|||
self.storage.shutdown()
|
||||
}
|
||||
|
||||
pub fn get_paths<P: Path>(&self, path: Option<P>) -> Result<Vec<String>, io::Error> {
|
||||
if let Some(path) = path {
|
||||
self.storage.paths(Some(&path.to_db_path()))
|
||||
} else {
|
||||
self.storage.paths(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_paths<P: Path>(&self, path: Option<P>) -> Result<Vec<String>, io::Error> {
|
||||
if let Some(path) = path {
|
||||
self.storage.all_paths(Some(&path.to_db_path()))
|
||||
} else {
|
||||
self.storage.all_paths(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_paths_of<T: ComObj, P: Path>(&self, path: P) -> Result<Vec<String>, io::Error> {
|
||||
T::paths(path, self)
|
||||
}
|
||||
|
||||
/// Sets an item in the database at the path.
|
||||
/// Here, the item is saved in a single blob at the path.
|
||||
pub fn set_raw<T: RawObj, P: Path>(&self, path: P, object: T) -> Result<(), io::Error> {
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ impl AllocationTable {
|
|||
for _ in 0..free_len {
|
||||
free.push((deserialize_u64!(f, buf64), deserialize_u64!(f, buf64)));
|
||||
}
|
||||
let mut map = HashMap::with_capacity(256);
|
||||
let mut map = HashMap::with_capacity(1024);
|
||||
for _ in 0..map_len {
|
||||
let str_len = deserialize_u64!(f, buf64);
|
||||
let mut buf = vec![0_u8; str_len];
|
||||
|
|
@ -160,9 +160,7 @@ impl AllocationTable {
|
|||
while i < (index + 1).min(self.free.len() - 1) {
|
||||
if self.free[i].0 + self.free[i].1 == self.free[i + 1].0 {
|
||||
self.free[i].1 += self.free.remove(i + 1).1;
|
||||
if index > 0 {
|
||||
index -= 1;
|
||||
}
|
||||
index = index.saturating_sub(1);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
|
@ -292,7 +290,7 @@ impl FAlloc {
|
|||
cache_period,
|
||||
data,
|
||||
alloc,
|
||||
cache: HashMap::with_capacity(256),
|
||||
cache: HashMap::with_capacity(1024),
|
||||
last_cache_check: 0,
|
||||
shutdown: false,
|
||||
}));
|
||||
|
|
@ -392,7 +390,7 @@ impl FAlloc {
|
|||
block_size,
|
||||
blocks_reserved: 0,
|
||||
free: Vec::new(),
|
||||
map: HashMap::with_capacity(256),
|
||||
map: HashMap::with_capacity(1024),
|
||||
},
|
||||
cache_period,
|
||||
)
|
||||
|
|
@ -478,6 +476,51 @@ impl FAlloc {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn paths(&self, path: Option<&str>) -> Result<Vec<String>, io::Error> {
|
||||
let mut this = self.inner.lock().unwrap();
|
||||
if this.shutdown {
|
||||
return Err(io::Error::new(ErrorKind::BrokenPipe, "The database has shut down. Writes are prohibited. If you didn't do this, some kind of error was encountered that forced the DB to shut down. Recovery will be attempted at regular intervals."));
|
||||
}
|
||||
let (alloc,) = deborrow!(this: alloc);
|
||||
if let Some(path) = path {
|
||||
let path_slashes = path.chars().filter(|x| x == &'/').count();
|
||||
Ok(alloc
|
||||
.map
|
||||
.keys()
|
||||
.filter(|x| {
|
||||
x.starts_with(&(path.to_owned() + "/"))
|
||||
&& x.chars().filter(|x| x == &'/').count() == path_slashes + 1
|
||||
})
|
||||
.map(|x| x.to_owned())
|
||||
.collect())
|
||||
} else {
|
||||
Ok(alloc
|
||||
.map
|
||||
.keys()
|
||||
.filter(|x| !x.contains('/'))
|
||||
.map(|x| x.to_owned())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_paths(&self, path: Option<&str>) -> Result<Vec<String>, io::Error> {
|
||||
let mut this = self.inner.lock().unwrap();
|
||||
if this.shutdown {
|
||||
return Err(io::Error::new(ErrorKind::BrokenPipe, "The database has shut down. Writes are prohibited. If you didn't do this, some kind of error was encountered that forced the DB to shut down. Recovery will be attempted at regular intervals."));
|
||||
}
|
||||
let (alloc,) = deborrow!(this: alloc);
|
||||
if let Some(path) = path {
|
||||
Ok(alloc
|
||||
.map
|
||||
.keys()
|
||||
.filter(|x| x.starts_with(&(path.to_owned() + "/")))
|
||||
.map(|x| x.to_owned())
|
||||
.collect())
|
||||
} else {
|
||||
Ok(alloc.map.keys().map(|x| x.to_owned()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
/// Deletes all data that is BELOW the path in the tree. The path itself is NOT deleted.
|
||||
pub fn delete_substructure(&self, path: &str) -> Result<(), io::Error> {
|
||||
let mut this = self.inner.lock().unwrap();
|
||||
|
|
@ -488,9 +531,9 @@ impl FAlloc {
|
|||
let time = SystemTime::UNIX_EPOCH.elapsed().unwrap().as_millis();
|
||||
for key in alloc
|
||||
.map
|
||||
.iter()
|
||||
.filter(|x| x.0.starts_with(&(path.to_owned() + "/")))
|
||||
.map(|x| x.0.to_owned())
|
||||
.keys()
|
||||
.filter(|x| x.starts_with(&(path.to_owned() + "/")))
|
||||
.map(|x| x.to_owned())
|
||||
{
|
||||
cache.insert(key, (time, true, Vec::new()));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue