use crate::store::{count, StoreError, Undo}; use chrono::{DateTime, Utc}; use sled::{Db, Transactional, Tree}; use uuid::Uuid; #[derive(Debug, Clone)] pub struct Relationship { pub id: Uuid, pub published: DateTime, pub left: Uuid, pub right: Uuid, } #[derive(Debug, Clone)] pub struct RelationshipStore { name: String, id_left: Tree, id_right: Tree, id_published: Tree, forward_id: Tree, forward_counts: Tree, forward_published_id: Tree, backward_id: Tree, backward_counts: Tree, backward_published_id: Tree, } #[derive(Debug, Clone)] struct RelationshipKeys { name: String, } impl RelationshipKeys { fn id_key(&self, id: Uuid) -> String { format!("/{}/id/{}/data", self.name, id) } fn forward_key(&self, left: Uuid, right: Uuid) -> String { format!("/{}/id/{}/forward/{}", self.name, left, right) } fn forward_published_key(&self, left: Uuid, published: DateTime, right: Uuid) -> String { format!( "/{}/id/{}/forward/published/{}/{}", self.name, left, published.to_rfc3339(), right ) } fn forward_published_prefix(&self, left: Uuid) -> String { format!("/{}/id/{}/forward/published", self.name, left) } fn forward_counts_key(&self, left: Uuid) -> String { format!("/{}/id/{}/forward/counts", self.name, left) } fn backward_key(&self, right: Uuid, left: Uuid) -> String { format!("/{}/id/{}/backward/{}", self.name, right, left) } fn backward_published_key(&self, right: Uuid, published: DateTime, left: Uuid) -> String { format!( "/{}/id/{}/backward/published/{}/{}", self.name, right, published.to_rfc3339(), left ) } fn backward_published_prefix(&self, right: Uuid) -> String { format!("/{}/id/{}/backward/published", self.name, right) } fn backward_counts_key(&self, right: Uuid) -> String { format!("/{}/id/{}/backward/counts", self.name, right) } } impl RelationshipStore { fn keys(&self) -> RelationshipKeys { RelationshipKeys { name: self.name.clone(), } } pub(super) fn build(name: &str, db: &Db) -> Result { Ok(RelationshipStore { id_left: db.open_tree(&format!("/profiles/relationship/{}/id/left", name))?, id_right: db.open_tree(&format!("/profiles/relationship/{}/id/right", name))?, id_published: db.open_tree(&format!("/profiles/relationship/{}/id/published", name))?, forward_id: db.open_tree(&format!("/profiles/relationship/{}/forward/id", name))?, forward_published_id: db.open_tree(&format!( "/profiles/relationship/{}/forward/published/id", name ))?, forward_counts: db .open_tree(&format!("/profiles/relationship/{}/forward/counts", name))?, backward_id: db.open_tree(&format!("/profiles/relationship/{}/backward/id", name))?, backward_published_id: db.open_tree(&format!( "/profiles/relationship/{}/backward/published/id", name ))?, backward_counts: db .open_tree(&format!("/profiles/relationship/{}/backward/counts", name))?, name: name.to_owned(), }) } pub fn new( &self, left_id: Uuid, right_id: Uuid, published: DateTime, ) -> Result { let keys = self.keys(); let (id, published) = [ &self.id_left, &self.id_right, &self.id_published, &self.forward_id, &self.forward_published_id, &self.forward_counts, &self.backward_id, &self.backward_published_id, &self.backward_counts, ] .transaction(move |trees| { let id_left = &trees[0]; let id_right = &trees[1]; let id_published = &trees[2]; let forward_id = &trees[3]; let forward_published_id = &trees[4]; let forward_counts = &trees[5]; let backward_id = &trees[6]; let backward_published_id = &trees[7]; let backward_counts = &trees[8]; let opt = forward_id .get(keys.forward_key(left_id, right_id).as_bytes())? .and_then(|ivec| Uuid::from_slice(&ivec).ok()); let opt = match opt { Some(id) => id_published .get(keys.id_key(id).as_bytes())? .and_then(|ivec| String::from_utf8_lossy(&ivec).parse::>().ok()) .map(|p| (id, p)), None => None, }; let (id, published) = if let Some((id, published)) = opt { (id, published) } else { (Uuid::new_v4(), published) }; id_left.insert(keys.id_key(id).as_bytes(), left_id.as_bytes())?; id_right.insert(keys.id_key(id).as_bytes(), right_id.as_bytes())?; id_published.insert( keys.id_key(id).as_bytes(), published.to_rfc3339().as_bytes(), )?; forward_id.insert( keys.forward_key(left_id, right_id).as_bytes(), id.as_bytes(), )?; backward_id.insert( keys.backward_key(right_id, left_id).as_bytes(), id.as_bytes(), )?; forward_published_id.insert( keys.forward_published_key(left_id, published, right_id) .as_bytes(), right_id.as_bytes(), )?; backward_published_id.insert( keys.backward_published_key(right_id, published, left_id) .as_bytes(), left_id.as_bytes(), )?; count(forward_counts, &keys.forward_counts_key(left_id), |c| { c.saturating_add(1) })?; count(backward_counts, &keys.backward_counts_key(right_id), |c| { c.saturating_add(1) })?; Ok((id, published)) })?; Ok(Relationship { id, published, left: left_id, right: right_id, }) } pub fn forward_iter(&self, left_id: Uuid) -> impl DoubleEndedIterator { let keys = self.keys(); self.forward_published_id .scan_prefix(keys.forward_published_prefix(left_id)) .values() .filter_map(|res| res.ok()) .filter_map(|ivec| Uuid::from_slice(&ivec).ok()) } pub fn backward_iter(&self, right_id: Uuid) -> impl DoubleEndedIterator { let keys = self.keys(); self.backward_published_id .scan_prefix(keys.backward_published_prefix(right_id)) .values() .filter_map(|res| res.ok()) .filter_map(|ivec| Uuid::from_slice(&ivec).ok()) } pub fn by_forward(&self, left_id: Uuid, right_id: Uuid) -> Result, StoreError> { let keys = self.keys(); let opt = self .forward_id .get(keys.forward_key(left_id, right_id))? .and_then(|ivec| Uuid::from_slice(&ivec).ok()); Ok(opt) } pub fn left(&self, id: Uuid) -> Result, StoreError> { let keys = self.keys(); let opt = self .id_left .get(keys.id_key(id))? .and_then(|ivec| Uuid::from_slice(&ivec).ok()); Ok(opt) } pub fn right(&self, id: Uuid) -> Result, StoreError> { let keys = self.keys(); let opt = self .id_right .get(keys.id_key(id))? .and_then(|ivec| Uuid::from_slice(&ivec).ok()); Ok(opt) } pub fn published(&self, id: Uuid) -> Result>, StoreError> { let keys = self.keys(); let opt = self .id_published .get(keys.id_key(id))? .and_then(|ivec| String::from_utf8_lossy(&ivec).parse::>().ok()); Ok(opt) } pub fn by_id(&self, id: Uuid) -> Result, StoreError> { let left_opt = self.left(id)?; let right_opt = self.right(id)?; let published_opt = self.published(id)?; let opt = left_opt.and_then(|left| { right_opt.and_then(|right| { published_opt.map(|published| Relationship { id, left, right, published, }) }) }); Ok(opt) } pub fn remove(&self, id: Uuid) -> Result>, StoreError> { let keys = self.keys(); let left_id = match self.left(id)? { Some(id) => id, None => return Ok(None), }; let right_id = match self.right(id)? { Some(id) => id, None => return Ok(None), }; let published = match self.published(id)? { Some(id) => id, None => return Ok(None), }; [ &self.id_left, &self.id_right, &self.id_published, &self.forward_id, &self.forward_published_id, &self.forward_counts, &self.backward_id, &self.backward_published_id, &self.backward_counts, ] .transaction(move |trees| { let id_left = &trees[0]; let id_right = &trees[1]; let id_published = &trees[2]; let forward_id = &trees[3]; let forward_published_id = &trees[4]; let forward_counts = &trees[5]; let backward_id = &trees[6]; let backward_published_id = &trees[7]; let backward_counts = &trees[8]; id_left.remove(keys.id_key(id).as_bytes())?; id_right.remove(keys.id_key(id).as_bytes())?; id_published.remove(keys.id_key(id).as_bytes())?; forward_id.remove(keys.forward_key(left_id, right_id).as_bytes())?; backward_id.remove(keys.backward_key(right_id, left_id).as_bytes())?; forward_published_id.remove( keys.forward_published_key(left_id, published, right_id) .as_bytes(), )?; backward_published_id.remove( keys.backward_published_key(right_id, published, left_id) .as_bytes(), )?; count(forward_counts, &keys.forward_counts_key(left_id), |c| { c.saturating_sub(1) })?; count(backward_counts, &keys.backward_counts_key(right_id), |c| { c.saturating_sub(1) })?; Ok(()) })?; Ok(Some(Undo(Relationship { id, left: left_id, right: right_id, published, }))) } }