2021-08-18 11:06:36 +02:00
|
|
|
use std::convert::TryFrom;
|
2021-12-04 18:33:40 +01:00
|
|
|
use std::sync::{Arc, Mutex};
|
2021-08-18 11:06:36 +02:00
|
|
|
|
|
|
|
use anyhow::{anyhow, Result};
|
|
|
|
use log::trace;
|
2021-12-04 18:33:40 +01:00
|
|
|
use lru::LruCache;
|
2021-08-18 11:06:36 +02:00
|
|
|
use serde_json::Value;
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
2021-12-17 19:44:00 +01:00
|
|
|
use crate::addressing::{Address, Addressable};
|
2021-08-18 11:06:36 +02:00
|
|
|
use crate::database::constants::{
|
2021-12-02 21:30:11 +01:00
|
|
|
HIER_ADDR, HIER_HAS_ATTR, HIER_INVARIANT, IS_OF_TYPE_ATTR, LABEL_ATTR, TYPE_ADDR, TYPE_HAS_ATTR,
|
2021-08-18 11:06:36 +02:00
|
|
|
};
|
2021-12-02 21:52:43 +01:00
|
|
|
use crate::database::entry::{Entry, EntryValue};
|
2021-08-18 11:06:36 +02:00
|
|
|
use crate::database::lang::{EntryQuery, Query, QueryComponent, QueryPart};
|
2021-12-23 11:10:16 +01:00
|
|
|
|
|
|
|
use super::UpEndConnection;
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-04 18:33:40 +01:00
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
2021-08-18 11:06:37 +02:00
|
|
|
pub struct UNode(String);
|
|
|
|
|
|
|
|
impl UNode {
|
|
|
|
pub fn new<T: Into<String>>(s: T) -> Result<Self> {
|
|
|
|
let s = s.into();
|
|
|
|
|
|
|
|
if s.is_empty() {
|
|
|
|
return Err(anyhow!("UNode can not be empty."));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self(s))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn as_ref(&self) -> &String {
|
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
2021-08-18 11:06:36 +02:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2021-12-17 23:04:35 +01:00
|
|
|
pub struct UHierPath(pub Vec<UNode>);
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
impl std::str::FromStr for UHierPath {
|
2021-08-18 11:06:36 +02:00
|
|
|
type Err = anyhow::Error;
|
|
|
|
|
|
|
|
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
|
|
|
if string.is_empty() {
|
2021-12-17 23:04:35 +01:00
|
|
|
Ok(UHierPath(vec![]))
|
2021-08-18 11:06:36 +02:00
|
|
|
} else {
|
2021-08-18 11:06:37 +02:00
|
|
|
let result: Result<Vec<UNode>> = string
|
2021-08-18 11:06:36 +02:00
|
|
|
.trim_end_matches('/')
|
|
|
|
.split('/')
|
2021-08-18 11:06:37 +02:00
|
|
|
.map(|part| UNode::new(String::from(part)))
|
2021-08-18 11:06:36 +02:00
|
|
|
.collect();
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
Ok(UHierPath(result?))
|
2021-08-18 11:06:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::fmt::Display for UNode {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(f, "{}", self.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
impl std::fmt::Display for UHierPath {
|
2021-08-18 11:06:36 +02:00
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(
|
|
|
|
f,
|
|
|
|
"{}",
|
|
|
|
self.0
|
|
|
|
.iter()
|
|
|
|
.map(|node| node.to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join("/")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
trait PointerEntries {
|
2021-12-17 19:44:00 +01:00
|
|
|
fn extract_pointers(&self) -> Vec<(Address, Address)>;
|
2021-08-18 11:06:36 +02:00
|
|
|
}
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
impl PointerEntries for Vec<Entry> {
|
2021-12-17 19:44:00 +01:00
|
|
|
fn extract_pointers(&self) -> Vec<(Address, Address)> {
|
2021-08-18 11:06:36 +02:00
|
|
|
self.iter()
|
|
|
|
.filter_map(|e| {
|
|
|
|
if let EntryValue::Address(address) = &e.value {
|
2021-12-17 19:44:00 +01:00
|
|
|
Some((e.address().unwrap(), address.clone()))
|
2021-08-18 11:06:36 +02:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
pub fn list_roots(connection: &UpEndConnection) -> Result<Vec<Address>> {
|
|
|
|
let all_directories: Vec<Entry> = connection.query(
|
2021-12-02 21:52:43 +01:00
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Exact(IS_OF_TYPE_ATTR.to_string()),
|
|
|
|
value: QueryComponent::Exact(EntryValue::Address(HIER_ADDR.clone())),
|
|
|
|
})),
|
|
|
|
)?;
|
|
|
|
|
2021-12-18 00:14:47 +01:00
|
|
|
// TODO: this is horrible
|
2021-12-23 11:10:16 +01:00
|
|
|
let directories_with_parents: Vec<Address> = connection.query(
|
2021-12-02 21:52:43 +01:00
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
|
|
|
attribute: QueryComponent::Exact(HIER_HAS_ATTR.to_string()),
|
|
|
|
value: QueryComponent::Any,
|
|
|
|
})),
|
|
|
|
)?
|
2021-12-17 19:44:00 +01:00
|
|
|
.extract_pointers()
|
|
|
|
.into_iter()
|
|
|
|
.map(|(_, val)| val)
|
|
|
|
.collect();
|
2021-12-02 21:52:43 +01:00
|
|
|
|
|
|
|
Ok(all_directories
|
|
|
|
.into_iter()
|
|
|
|
.filter(|entry| !directories_with_parents.contains(&entry.entity))
|
|
|
|
.map(|e| e.entity)
|
|
|
|
.collect())
|
|
|
|
}
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-05 22:36:16 +01:00
|
|
|
lazy_static! {
|
|
|
|
static ref FETCH_CREATE_LOCK: Mutex<()> = Mutex::new(());
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
pub fn fetch_or_create_dir(
|
|
|
|
connection: &UpEndConnection,
|
2021-08-18 11:06:36 +02:00
|
|
|
parent: Option<Address>,
|
|
|
|
directory: UNode,
|
|
|
|
create: bool,
|
|
|
|
) -> Result<Address> {
|
|
|
|
match parent.clone() {
|
|
|
|
Some(address) => trace!("FETCHING/CREATING {}/{:#}", address, directory),
|
|
|
|
None => trace!("FETCHING/CREATING /{:#}", directory),
|
|
|
|
}
|
|
|
|
|
2021-12-05 22:36:16 +01:00
|
|
|
let _lock;
|
|
|
|
if create {
|
|
|
|
_lock = FETCH_CREATE_LOCK.lock().unwrap();
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
let matching_directories = connection.query(
|
2021-08-18 11:06:36 +02:00
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Any,
|
2021-12-02 21:30:11 +01:00
|
|
|
attribute: QueryComponent::Exact(String::from(LABEL_ATTR)),
|
2021-08-18 11:06:37 +02:00
|
|
|
value: QueryComponent::Exact(EntryValue::Value(Value::String(
|
|
|
|
directory.as_ref().clone(),
|
|
|
|
))),
|
2021-08-18 11:06:36 +02:00
|
|
|
})),
|
|
|
|
)?
|
|
|
|
.into_iter()
|
2021-10-28 17:34:01 +02:00
|
|
|
.map(|e: Entry| e.entity);
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-02 21:52:43 +01:00
|
|
|
let parent_has: Vec<Address> = match parent.clone() {
|
2021-12-23 11:10:16 +01:00
|
|
|
Some(parent) => connection.query(
|
2021-12-02 21:52:43 +01:00
|
|
|
Query::SingleQuery(QueryPart::Matches(EntryQuery {
|
|
|
|
entity: QueryComponent::Exact(parent),
|
|
|
|
attribute: QueryComponent::Exact(String::from(HIER_HAS_ATTR)),
|
|
|
|
value: QueryComponent::Any,
|
|
|
|
})),
|
|
|
|
)?
|
2021-12-17 19:44:00 +01:00
|
|
|
.extract_pointers()
|
|
|
|
.into_iter()
|
|
|
|
.map(|(_, val)| val)
|
|
|
|
.collect(),
|
2021-12-17 23:04:35 +01:00
|
|
|
None => list_roots(connection)?,
|
2021-12-02 21:52:43 +01:00
|
|
|
};
|
2021-08-18 11:06:39 +02:00
|
|
|
|
|
|
|
let valid_directories: Vec<Address> = matching_directories
|
|
|
|
.filter(|a| parent_has.contains(a))
|
|
|
|
.collect();
|
2021-08-18 11:06:36 +02:00
|
|
|
|
|
|
|
match valid_directories.len() {
|
|
|
|
0 => {
|
|
|
|
if create {
|
|
|
|
let new_directory_address = Address::Uuid(Uuid::new_v4());
|
|
|
|
let type_entry = Entry {
|
|
|
|
entity: new_directory_address.clone(),
|
|
|
|
attribute: String::from(IS_OF_TYPE_ATTR),
|
|
|
|
value: EntryValue::Address(HIER_ADDR.clone()),
|
|
|
|
};
|
2021-12-23 11:10:16 +01:00
|
|
|
connection.insert_entry(type_entry)?;
|
2021-08-18 11:06:36 +02:00
|
|
|
|
|
|
|
let directory_entry = Entry {
|
|
|
|
entity: new_directory_address.clone(),
|
2021-12-02 21:30:11 +01:00
|
|
|
attribute: String::from(LABEL_ATTR),
|
2021-08-18 11:06:37 +02:00
|
|
|
value: EntryValue::Value(Value::String(directory.as_ref().clone())),
|
2021-08-18 11:06:36 +02:00
|
|
|
};
|
2021-12-23 11:10:16 +01:00
|
|
|
connection.insert_entry(directory_entry)?;
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-04 16:46:13 +01:00
|
|
|
if let Some(parent) = parent {
|
2021-12-02 21:52:43 +01:00
|
|
|
let has_entry = Entry {
|
2021-12-04 16:46:13 +01:00
|
|
|
entity: parent,
|
2021-12-02 21:52:43 +01:00
|
|
|
attribute: String::from(HIER_HAS_ATTR),
|
|
|
|
value: EntryValue::Address(new_directory_address.clone()),
|
|
|
|
};
|
2021-12-23 11:10:16 +01:00
|
|
|
connection.insert_entry(has_entry)?;
|
2021-12-02 21:52:43 +01:00
|
|
|
}
|
2021-08-18 11:06:36 +02:00
|
|
|
|
|
|
|
Ok(new_directory_address)
|
|
|
|
} else {
|
2021-12-21 23:16:10 +01:00
|
|
|
Err(anyhow!("Node {:?} does not exist.", directory.0))
|
2021-08-18 11:06:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
1 => Ok(valid_directories[0].clone()),
|
2021-12-05 12:10:41 +01:00
|
|
|
_ => Err(anyhow!(format!(
|
2021-12-05 22:36:16 +01:00
|
|
|
"Invalid database state - more than one directory matches the query {:?}/{:#}!",
|
|
|
|
parent, directory
|
|
|
|
))),
|
2021-08-18 11:06:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
pub fn resolve_path(
|
|
|
|
connection: &UpEndConnection,
|
2021-12-17 23:04:35 +01:00
|
|
|
path: &UHierPath,
|
2021-08-18 11:06:36 +02:00
|
|
|
create: bool,
|
|
|
|
) -> Result<Vec<Address>> {
|
2021-12-02 21:52:43 +01:00
|
|
|
let mut result: Vec<Address> = vec![];
|
2021-08-18 11:06:36 +02:00
|
|
|
let mut path_stack = path.0.to_vec();
|
|
|
|
|
|
|
|
path_stack.reverse();
|
|
|
|
while !path_stack.is_empty() {
|
|
|
|
let dir_address = fetch_or_create_dir(
|
|
|
|
connection,
|
|
|
|
result.last().cloned(),
|
|
|
|
path_stack.pop().unwrap(),
|
|
|
|
create,
|
|
|
|
)?;
|
|
|
|
result.push(dir_address);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2021-12-04 18:33:40 +01:00
|
|
|
pub type ResolveCache = LruCache<(Option<Address>, UNode), Address>;
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
pub fn resolve_path_cached(
|
|
|
|
connection: &UpEndConnection,
|
2021-12-17 23:04:35 +01:00
|
|
|
path: &UHierPath,
|
2021-12-04 18:33:40 +01:00
|
|
|
create: bool,
|
|
|
|
cache: &Arc<Mutex<ResolveCache>>,
|
|
|
|
) -> Result<Vec<Address>> {
|
|
|
|
let mut result: Vec<Address> = vec![];
|
|
|
|
let mut path_stack = path.0.to_vec();
|
|
|
|
|
|
|
|
path_stack.reverse();
|
|
|
|
while !path_stack.is_empty() {
|
|
|
|
let node = path_stack.pop().unwrap();
|
|
|
|
let parent = result.last().cloned();
|
|
|
|
let key = (parent.clone(), node.clone());
|
|
|
|
let mut cache_lock = cache.lock().unwrap();
|
|
|
|
let cached_address = cache_lock.get(&key);
|
|
|
|
if let Some(address) = cached_address {
|
|
|
|
result.push(address.clone());
|
|
|
|
} else {
|
|
|
|
drop(cache_lock);
|
2021-12-05 22:36:16 +01:00
|
|
|
let address = fetch_or_create_dir(connection, parent, node, create)?;
|
2021-12-04 18:33:40 +01:00
|
|
|
result.push(address.clone());
|
|
|
|
cache.lock().unwrap().put(key, address);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
pub fn initialize_hier(connection: &UpEndConnection) -> Result<()> {
|
|
|
|
connection.insert_entry(Entry::try_from(&*HIER_INVARIANT)?)?;
|
2021-12-23 11:18:04 +01:00
|
|
|
upend_insert_addr!(connection, HIER_ADDR, IS_OF_TYPE_ATTR, TYPE_ADDR);
|
|
|
|
upend_insert_val!(connection, HIER_ADDR, TYPE_HAS_ATTR, HIER_HAS_ATTR);
|
2021-08-18 11:06:36 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use anyhow::Result;
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
use crate::database::UpEndDatabase;
|
2021-12-02 22:18:43 +01:00
|
|
|
use tempdir::TempDir;
|
|
|
|
|
|
|
|
use super::*;
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-08-18 11:06:37 +02:00
|
|
|
#[test]
|
|
|
|
fn test_unode_nonempty() {
|
|
|
|
let node = UNode::new("foobar");
|
|
|
|
assert!(node.is_ok());
|
|
|
|
|
|
|
|
let node = UNode::new("");
|
|
|
|
assert!(node.is_err());
|
|
|
|
}
|
|
|
|
|
2021-08-18 11:06:36 +02:00
|
|
|
#[test]
|
|
|
|
fn test_path_codec() {
|
2021-12-17 23:04:35 +01:00
|
|
|
let path = UHierPath(vec![
|
2021-08-18 11:06:36 +02:00
|
|
|
UNode("top".to_string()),
|
|
|
|
UNode("foo".to_string()),
|
|
|
|
UNode("bar".to_string()),
|
|
|
|
UNode("baz".to_string()),
|
|
|
|
]);
|
|
|
|
|
|
|
|
let str_path = path.to_string();
|
2021-10-28 17:34:01 +02:00
|
|
|
assert!(!str_path.is_empty());
|
2021-08-18 11:06:36 +02:00
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
let decoded_path: Result<UHierPath> = str_path.parse();
|
2021-08-18 11:06:36 +02:00
|
|
|
assert!(decoded_path.is_ok());
|
|
|
|
|
|
|
|
assert_eq!(path, decoded_path.unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-12-02 22:18:43 +01:00
|
|
|
fn test_path_validation() {
|
2021-12-17 23:04:35 +01:00
|
|
|
let valid_path: Result<UHierPath> = "a/b/c/d/e/f/g".parse();
|
2021-08-18 11:06:36 +02:00
|
|
|
assert!(valid_path.is_ok());
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
let invalid_path: Result<UHierPath> = "a/b/c//d/e/f/g".parse();
|
2021-08-18 11:06:36 +02:00
|
|
|
assert!(invalid_path.is_err());
|
|
|
|
|
2021-12-17 23:04:35 +01:00
|
|
|
let invalid_path: Result<UHierPath> = "a//b/c//d/e/f///g".parse();
|
2021-08-18 11:06:36 +02:00
|
|
|
assert!(invalid_path.is_err());
|
|
|
|
}
|
2021-12-02 22:18:43 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_path_manipulation() {
|
|
|
|
// Initialize database
|
|
|
|
let temp_dir = TempDir::new("upend-test").unwrap();
|
2021-12-23 11:10:16 +01:00
|
|
|
let open_result = UpEndDatabase::open(&temp_dir, None, true).unwrap();
|
|
|
|
let connection = open_result.db.connection().unwrap();
|
2021-12-02 22:18:43 +01:00
|
|
|
|
|
|
|
let foo_result = fetch_or_create_dir(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
None,
|
|
|
|
UNode("foo".to_string()),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
assert!(foo_result.is_ok());
|
2021-12-21 11:26:20 +01:00
|
|
|
let foo_result = foo_result.unwrap();
|
2021-12-02 22:18:43 +01:00
|
|
|
|
|
|
|
let bar_result = fetch_or_create_dir(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
None,
|
|
|
|
UNode("bar".to_string()),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
assert!(bar_result.is_ok());
|
|
|
|
let bar_result = bar_result.unwrap();
|
|
|
|
|
|
|
|
let baz_result = fetch_or_create_dir(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
Some(bar_result.clone()),
|
|
|
|
UNode("baz".to_string()),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
assert!(baz_result.is_ok());
|
|
|
|
let baz_result = baz_result.unwrap();
|
|
|
|
|
2021-12-23 11:10:16 +01:00
|
|
|
let roots = list_roots(&connection);
|
2021-12-21 11:26:20 +01:00
|
|
|
assert_eq!(roots.unwrap(), [foo_result, bar_result.clone()]);
|
2021-12-02 22:18:43 +01:00
|
|
|
|
|
|
|
let resolve_result = resolve_path(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
&"bar/baz".parse().unwrap(),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(resolve_result.is_ok());
|
|
|
|
assert_eq!(
|
|
|
|
resolve_result.unwrap(),
|
|
|
|
vec![bar_result.clone(), baz_result.clone()]
|
|
|
|
);
|
|
|
|
|
|
|
|
let resolve_result = resolve_path(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
&"bar/baz/bax".parse().unwrap(),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
assert!(resolve_result.is_err());
|
|
|
|
|
|
|
|
let resolve_result = resolve_path(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
&"bar/baz/bax".parse().unwrap(),
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
assert!(resolve_result.is_ok());
|
|
|
|
|
|
|
|
let bax_result = fetch_or_create_dir(
|
2021-12-23 11:10:16 +01:00
|
|
|
&connection,
|
2021-12-02 22:18:43 +01:00
|
|
|
Some(baz_result.clone()),
|
|
|
|
UNode("bax".to_string()),
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
assert!(bax_result.is_ok());
|
|
|
|
let bax_result = bax_result.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
resolve_result.unwrap(),
|
|
|
|
vec![bar_result, baz_result, bax_result]
|
|
|
|
);
|
|
|
|
}
|
2021-08-18 11:06:36 +02:00
|
|
|
}
|