use std::{collections::HashSet, hash::Hash};
use crate::{
builtin_idents, storage::Storage, AttributeIterator, Connection, Datom, DatomType,
EntityResult, QueryError, Value, EID, ID,
};
#[derive(Debug, Eq, Clone, Copy)]
pub struct Entity<'connection, S: Storage> {
pub(crate) connection: &'connection Connection<S>,
pub(crate) t: u64,
pub(crate) id: ID,
}
impl<'connection, S: Storage> PartialEq<Self> for Entity<'connection, S> {
fn eq(&self, other: &Self) -> bool {
self.connection == other.connection && self.t == other.t && self.id == other.id
}
}
impl<'connection, S: Storage> Entity<'connection, S> {
pub fn id(&self) -> &ID {
&self.id
}
pub fn get_with_options(
&self,
attribute: EID,
skip_cardinality: bool,
skip_type: bool,
) -> Result<EntityResult<'connection, S>, QueryError> {
let db = self.connection.as_of(self.t)?;
let attribute = attribute.resolve(&db)?;
if attribute == builtin_idents::ID {
return Ok(Value::from(self.id).into());
}
let attribute_ent = db.entity(attribute.into())?;
let is_repeated = !skip_cardinality
&& attribute_ent
.get_with_options(builtin_idents::CARDINALITY.into(), true, false)?
.is_ref_to(&builtin_idents::CARDINALITY_MANY);
let attribute_type = {
if skip_type {
None
} else {
let attribute_type = attribute_ent.get_with_options(
builtin_idents::VALUE_TYPE.into(),
true,
true,
)?;
if let EntityResult::Value(Value::ID(t)) = attribute_type {
Some(t)
} else {
None
}
}
};
let result = if is_repeated {
let datoms = db.datoms_for_entity_attribute(self.id, attribute)?;
let mut values = HashSet::new();
for datom in datoms {
if datom.datom_type == DatomType::Retraction {
values.remove(&datom.value);
} else {
values.insert(datom.value);
}
}
let res: Result<Vec<EntityResult<'connection, S>>, QueryError> = values
.into_iter()
.map(|v| {
if attribute_type == Some(builtin_idents::TYPE_REF) {
if let Value::ID(id) = v {
Ok(EntityResult::Ref(db.entity(id.into())?))
} else {
Ok(EntityResult::Value(v))
}
} else {
Ok(EntityResult::from(v))
}
})
.collect();
EntityResult::Repeated(res?)
} else {
db.datoms_for_entity_attribute(self.id, attribute)?
.max_by(|a, b| a.t.cmp(&b.t))
.map(|x| -> Result<EntityResult<'connection, S>, QueryError> {
if x.datom_type == DatomType::Retraction {
Ok(EntityResult::NotFound)
} else if attribute_type == Some(builtin_idents::TYPE_REF) {
if let Value::ID(id) = x.value {
Ok(EntityResult::Ref(db.entity(id.into())?))
} else {
Ok(EntityResult::Value(x.value))
}
} else {
Ok(EntityResult::Value(x.value))
}
})
.unwrap_or(Ok(EntityResult::NotFound))?
};
if let EntityResult::NotFound = result {
let builtin = builtin_idents::BUILTIN_ENTITIES.get(&self.id);
if let Some(builtin) = builtin {
let val = builtin.get(&attribute);
if let Some(val) = val {
Ok(val.to_owned().into())
} else {
Ok(EntityResult::NotFound)
}
} else {
Ok(EntityResult::NotFound)
}
} else {
Ok(result)
}
}
pub fn get(&self, attribute: EID) -> Result<EntityResult<'connection, S>, QueryError> {
self.get_with_options(attribute, false, false)
}
pub fn reverse_get(&self, attribute: EID) -> Result<EntityResult<'connection, S>, QueryError> {
let db = self.connection.as_of(self.t)?;
let attribute = attribute.resolve(&db)?;
let datoms = db.datoms_for_value_attribute(self.id().to_owned().into(), attribute)?;
let datoms: Vec<Datom> = datoms.collect();
let mut entities = HashSet::new();
for datom in datoms {
if datom.datom_type == DatomType::Retraction {
entities.remove(&datom.entity);
} else {
entities.insert(datom.entity);
}
}
let res: Result<Vec<EntityResult<'connection, S>>, QueryError> = entities
.into_iter()
.map(|id| Ok(EntityResult::Ref(db.entity(id.into())?)))
.collect();
Ok(EntityResult::Repeated(res?))
}
pub fn attributes(&self) -> Result<AttributeIterator<'connection>, QueryError> {
let iter = self.connection.as_of(self.t)?.datoms_for_entity(self.id)?;
AttributeIterator::new(iter)
}
}
impl<'connection, S: Storage> Hash for Entity<'connection, S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.t.hash(state);
self.id.hash(state);
}
}