use super::{BlockKind, BlockSource, PayloadCursor, PayloadSlice, TreeError, TreeResources};
use crate::property_graph::{
ApplicationKey, EntityId, EntityKind, ExpectedGraphState, GraphDeleteMode, GraphGeneration,
GraphOperation, GraphRevision, MAX_GRAPH_INPUT_BYTES, NodeId, OperationFields, RelId,
};
use std::cmp::Ordering;
pub struct StoredKey<'a, S: BlockSource> {
kind: EntityKind,
namespace: PayloadSlice<'a, S>,
key: PayloadSlice<'a, S>,
}
impl<S: BlockSource> Copy for StoredKey<'_, S> {}
impl<S: BlockSource> Clone for StoredKey<'_, S> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, S: BlockSource> StoredKey<'a, S> {
pub const fn kind(self) -> EntityKind {
self.kind
}
pub const fn namespace(self) -> PayloadSlice<'a, S> {
self.namespace
}
pub const fn key(self) -> PayloadSlice<'a, S> {
self.key
}
}
pub struct StoredProvenance<'a, S: BlockSource> {
operation: GraphOperation,
key: Option<StoredKey<'a, S>>,
requested_revision: GraphRevision,
installed_revision: GraphRevision,
expected: ExpectedGraphState,
incarnation: EntityId,
delete_mode: Option<GraphDeleteMode>,
original_generation: GraphGeneration,
}
impl<'a, S: BlockSource> StoredProvenance<'a, S> {
pub const fn operation(&self) -> GraphOperation {
self.operation
}
pub const fn key(&self) -> Option<StoredKey<'a, S>> {
self.key
}
pub const fn requested_revision(&self) -> GraphRevision {
self.requested_revision
}
pub const fn installed_revision(&self) -> GraphRevision {
self.installed_revision
}
pub const fn expected(&self) -> ExpectedGraphState {
self.expected
}
pub const fn incarnation(&self) -> EntityId {
self.incarnation
}
pub const fn delete_mode(&self) -> Option<GraphDeleteMode> {
self.delete_mode
}
pub const fn original_generation(&self) -> GraphGeneration {
self.original_generation
}
pub fn fields_with_key<'k>(
&self,
key: Option<ApplicationKey<'k>>,
resources: &mut TreeResources<'_>,
) -> Result<OperationFields<'k>, TreeError> {
resources.step(1)?;
match (self.key, key) {
(None, None) => {}
(Some(stored), Some(key))
if stored.kind == key.kind()
&& stored
.namespace
.compare_bytes(key.namespace().as_str().as_bytes(), resources)?
== Ordering::Equal
&& stored
.key
.compare_bytes(key.key().as_str().as_bytes(), resources)?
== Ordering::Equal => {}
_ => {
return Err(TreeError::Invalid(
"provenance key differs from retained fields",
));
}
}
resources.step(0)?;
Ok(OperationFields {
operation: self.operation,
key,
requested_revision: self.requested_revision,
installed_revision: self.installed_revision,
expected: self.expected,
incarnation: self.incarnation,
delete_mode: self.delete_mode,
original_generation: self.original_generation,
})
}
}
pub fn verify_provenance<'a, S: BlockSource>(
source: PayloadSlice<'a, S>,
resources: &mut TreeResources<'_>,
) -> Result<StoredProvenance<'a, S>, TreeError> {
resources.step(1)?;
if !source.is_whole()
|| source.role() != BlockKind::OperationProvenance
|| source.len() > MAX_GRAPH_INPUT_BYTES as u64
{
return Err(TreeError::Invalid("provenance role or bound"));
}
let mut cursor = PayloadCursor::new_with_resources(source, resources)?;
if cursor.read_array::<4>(resources)? != *b"ZGOP"
|| u16::from_le_bytes(cursor.read_array(resources)?) != 1
{
return Err(TreeError::Invalid("provenance magic/version"));
}
let operation = match super::byte(&mut cursor, resources)? {
1 => GraphOperation::StructuredCreate,
2 => GraphOperation::StructuredPut,
3 => GraphOperation::StructuredDelete,
4 => GraphOperation::StructuredRecreate,
5 => GraphOperation::CypherEdit,
_ => return Err(TreeError::Invalid("provenance operation tag")),
};
let key = match super::byte(&mut cursor, resources)? {
0 => None,
1 => Some(StoredKey {
kind: kind(&mut cursor, resources)?,
namespace: super::text(&mut cursor, resources)?,
key: super::text(&mut cursor, resources)?,
}),
_ => return Err(TreeError::Invalid("provenance key presence")),
};
let requested_revision = revision(&mut cursor, resources)?;
let installed_revision = revision(&mut cursor, resources)?;
let expected = match super::byte(&mut cursor, resources)? {
1 => ExpectedGraphState::Absent,
2 => ExpectedGraphState::Entity(entity(&mut cursor, resources)?),
3 => ExpectedGraphState::Deletion(revision(&mut cursor, resources)?),
_ => return Err(TreeError::Invalid("provenance expected state tag")),
};
let incarnation = entity(&mut cursor, resources)?;
let delete_mode = match super::byte(&mut cursor, resources)? {
0 => None,
1 => Some(GraphDeleteMode::Restrict),
2 => Some(GraphDeleteMode::Detach),
_ => return Err(TreeError::Invalid("provenance delete tag")),
};
let original_generation =
GraphGeneration::new(u64::from_le_bytes(cursor.read_array(resources)?));
if key.is_some_and(|k| k.kind != incarnation.kind())
|| matches!(expected, ExpectedGraphState::Entity(id) if id.kind() != incarnation.kind())
{
return Err(TreeError::Invalid("provenance entity kind mismatch"));
}
cursor.finish(resources)?;
Ok(StoredProvenance {
operation,
key,
requested_revision,
installed_revision,
expected,
incarnation,
delete_mode,
original_generation,
})
}
fn kind<S: BlockSource>(
cursor: &mut PayloadCursor<'_, '_, S>,
resources: &mut TreeResources<'_>,
) -> Result<EntityKind, TreeError> {
match super::byte(cursor, resources)? {
1 => Ok(EntityKind::Node),
2 => Ok(EntityKind::Relationship),
_ => Err(TreeError::Invalid("provenance entity kind")),
}
}
fn revision<S: BlockSource>(
cursor: &mut PayloadCursor<'_, '_, S>,
resources: &mut TreeResources<'_>,
) -> Result<GraphRevision, TreeError> {
GraphRevision::new(u64::from_le_bytes(cursor.read_array(resources)?))
.map_err(|_| TreeError::Invalid("zero provenance revision"))
}
fn entity<S: BlockSource>(
cursor: &mut PayloadCursor<'_, '_, S>,
resources: &mut TreeResources<'_>,
) -> Result<EntityId, TreeError> {
let kind = kind(cursor, resources)?;
let bits = u128::from_le_bytes(cursor.read_array(resources)?);
match kind {
EntityKind::Node => Ok(EntityId::Node(NodeId::from(crate::ingest::DocId::new(
bits,
)))),
EntityKind::Relationship => RelId::new(bits).map(EntityId::Relationship),
}
.map_err(|_| TreeError::Invalid("zero provenance incarnation"))
}