add a way to list paths

This commit is contained in:
TudbuT 2023-06-24 15:30:00 +02:00
parent f777ed1a4f
commit 010c2492e2
8 changed files with 115 additions and 23 deletions

View file

@ -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"

View file

@ -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()

View file

@ -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")])
}
}
}

View file

@ -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")])
}
}

View file

@ -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

View file

@ -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())
}
}

View file

@ -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> {

View file

@ -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()));
}