use std::collections::HashSet;
use loro::{LoroDoc, LoroMap, LoroValue, ValueOrContainer};
use crate::error::{CrdtError, Result};
use crate::validator::bitemporal::{VALID_UNTIL, VALID_UNTIL_OPEN};
fn row_is_live(row: &LoroMap) -> bool {
match row.get(VALID_UNTIL) {
None => true,
Some(ValueOrContainer::Value(LoroValue::Null)) => true,
Some(ValueOrContainer::Value(LoroValue::I64(n))) => n == VALID_UNTIL_OPEN,
_ => true,
}
}
pub struct CrdtState {
pub(super) doc: LoroDoc,
pub(super) peer_id: u64,
array_surrogate_ids: HashSet<String>,
}
impl CrdtState {
pub fn new(peer_id: u64) -> Result<Self> {
let doc = LoroDoc::new();
doc.set_peer_id(peer_id)
.map_err(|e| CrdtError::Loro(format!("failed to set peer_id {peer_id}: {e}")))?;
Ok(Self {
doc,
peer_id,
array_surrogate_ids: HashSet::new(),
})
}
pub fn register_array_surrogate(&mut self, id: String) {
self.array_surrogate_ids.insert(id);
}
pub fn upsert(
&self,
collection: &str,
row_id: &str,
fields: &[(&str, LoroValue)],
) -> Result<()> {
let coll = self.doc.get_map(collection);
let row_container = coll
.insert_container(row_id, LoroMap::new())
.map_err(|e| CrdtError::Loro(e.to_string()))?;
for (field, value) in fields {
row_container
.insert(field, value.clone())
.map_err(|e| CrdtError::Loro(e.to_string()))?;
}
Ok(())
}
pub fn delete(&self, collection: &str, row_id: &str) -> Result<()> {
let coll = self.doc.get_map(collection);
coll.delete(row_id)
.map_err(|e| CrdtError::Loro(e.to_string()))?;
Ok(())
}
pub fn clear_collection(&self, collection: &str) -> Result<usize> {
let coll = self.doc.get_map(collection);
let keys: Vec<String> = coll.keys().map(|k| k.to_string()).collect();
let count = keys.len();
for key in &keys {
coll.delete(key)
.map_err(|e| CrdtError::Loro(e.to_string()))?;
}
Ok(count)
}
pub fn read_row(&self, collection: &str, row_id: &str) -> Option<LoroValue> {
let coll = self.doc.get_map(collection);
match coll.get(row_id)? {
ValueOrContainer::Container(loro::Container::Map(m)) => Some(m.get_value()),
ValueOrContainer::Container(loro::Container::List(l)) => Some(l.get_value()),
ValueOrContainer::Container(_) => Some(LoroValue::Null),
ValueOrContainer::Value(v) => Some(v),
}
}
pub fn read_field(&self, collection: &str, row_id: &str, field: &str) -> Option<LoroValue> {
let coll = self.doc.get_map(collection);
let row_map = match coll.get(row_id)? {
ValueOrContainer::Container(loro::Container::Map(m)) => m,
ValueOrContainer::Value(v) => return Some(v),
_ => return None,
};
match row_map.get(field)? {
ValueOrContainer::Value(v) => Some(v),
ValueOrContainer::Container(loro::Container::Map(m)) => Some(m.get_value()),
ValueOrContainer::Container(loro::Container::List(l)) => Some(l.get_value()),
ValueOrContainer::Container(_) => Some(LoroValue::Null),
}
}
pub fn row_exists(&self, collection: &str, row_id: &str) -> bool {
let coll = self.doc.get_map(collection);
if coll.get(row_id).is_some() {
return true;
}
self.array_surrogate_ids.contains(row_id)
}
pub fn collection_names(&self) -> Vec<String> {
let root = self.doc.get_deep_value();
match root {
LoroValue::Map(map) => map.keys().map(|k| k.to_string()).collect(),
_ => Vec::new(),
}
}
pub fn row_ids(&self, collection: &str) -> Vec<String> {
let coll = self.doc.get_map(collection);
coll.keys().map(|k| k.to_string()).collect()
}
pub fn field_value_exists(&self, collection: &str, field: &str, value: &LoroValue) -> bool {
let coll = self.doc.get_map(collection);
for key in coll.keys() {
let path = format!("{collection}/{key}/{field}");
if let Some(voc) = self.doc.get_by_str_path(&path) {
let field_val = match voc {
ValueOrContainer::Value(v) => v,
ValueOrContainer::Container(_) => {
continue;
}
};
if &field_val == value {
return true;
}
}
}
false
}
pub fn field_value_exists_live(
&self,
collection: &str,
field: &str,
value: &LoroValue,
) -> bool {
let coll = self.doc.get_map(collection);
for key in coll.keys() {
let row_map = match coll.get(&key) {
Some(ValueOrContainer::Container(loro::Container::Map(m))) => m,
_ => continue,
};
if !row_is_live(&row_map) {
continue;
}
let field_val = match row_map.get(field) {
Some(ValueOrContainer::Value(v)) => v,
_ => continue,
};
if &field_val == value {
return true;
}
}
false
}
pub fn live_row_ids(&self, collection: &str) -> Vec<String> {
let coll = self.doc.get_map(collection);
let mut out = Vec::new();
for key in coll.keys() {
let row_map = match coll.get(&key) {
Some(ValueOrContainer::Container(loro::Container::Map(m))) => m,
_ => continue,
};
if row_is_live(&row_map) {
out.push(key.to_string());
}
}
out
}
pub fn doc(&self) -> &LoroDoc {
&self.doc
}
pub fn peer_id(&self) -> u64 {
self.peer_id
}
}