Skip to main content

uqa_storage/mvcc/
graph.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Evaluated graph changes and provider record addressing for derived path state.
8
9mod resolve;
10
11use std::sync::Arc;
12
13use uqa_core::memory::BudgetedVec;
14
15use crate::{read_control::StorageReadControl, GraphEntityKind};
16
17use super::{CommitSequence, DatabaseId, MergedRecordSnapshot, VersionResult};
18
19/// A logical change whose derived path-cache effects must use current commit dependencies. These operations never execute graph algorithms or application callbacks.
20#[derive(Clone, Copy)]
21pub enum GraphMutation<'a> {
22    InvalidateGraph(&'a str),
23    InvalidatePath(&'a str),
24    InvalidateEntity(GraphEntityKind, u64),
25    PublishPath {
26        index: &'a str,
27        graph: &'a str,
28        definition: &'a str,
29    },
30}
31
32/// Logical addresses and scan prefixes; each provider owns its durable byte encoding.
33#[derive(Clone, Copy)]
34pub enum GraphRecordKey<'a> {
35    Entity(GraphEntityKind, u64),
36    EntityMemberships(GraphEntityKind, u64),
37    GraphMemberships(&'a str),
38    GraphPaths(&'a str),
39    GraphName(&'a str),
40    LabelRegistry(&'a str),
41    PathDefinition(&'a str),
42    PathValidity(&'a str),
43}
44
45/// Physical addressing and row codecs only. Common MVCC owns dependency discovery, invalidation order, build validation and commit retries.
46pub trait GraphRecordLayout: Send + Sync {
47    fn key(
48        &self,
49        database: DatabaseId,
50        key: GraphRecordKey<'_>,
51        control: &StorageReadControl,
52    ) -> VersionResult<BudgetedVec<u8>>;
53
54    fn is_validity_key(&self, key: &[u8]) -> VersionResult<bool>;
55
56    fn membership_graph(
57        &self,
58        key: &[u8],
59        control: &StorageReadControl,
60    ) -> VersionResult<BudgetedVec<u8>>;
61
62    fn membership_entity(
63        &self,
64        key: &[u8],
65        control: &StorageReadControl,
66    ) -> VersionResult<(GraphEntityKind, u64)>;
67
68    /// Decode one path-directory entry; return its validity key only when it belongs to the selected graph.
69    fn path_validity_key(
70        &self,
71        view: &MergedRecordSnapshot,
72        database: DatabaseId,
73        graph: &str,
74        key: &[u8],
75        value: &[u8],
76        control: &StorageReadControl,
77    ) -> VersionResult<Option<BudgetedVec<u8>>>;
78
79    fn invalidate(
80        &self,
81        key: &[u8],
82        value: &[u8],
83        control: &StorageReadControl,
84    ) -> VersionResult<Option<BudgetedVec<u8>>>;
85
86    /// Return whether the transaction's final private state still contains this completed build, rather than a later clear or invalidation.
87    fn is_published(
88        &self,
89        view: &MergedRecordSnapshot,
90        key: &[u8],
91        value: &[u8],
92        graph: &str,
93        definition: &str,
94        control: &StorageReadControl,
95    ) -> VersionResult<bool>;
96
97    fn definition_matches(
98        &self,
99        key: &[u8],
100        value: &[u8],
101        definition: &str,
102        control: &StorageReadControl,
103    ) -> VersionResult<bool>;
104}
105
106type Text = Arc<BudgetedVec<u8>>;
107
108#[derive(Clone)]
109pub(super) enum OwnedGraphMutation {
110    InvalidateGraph(Text),
111    InvalidatePath(Text),
112    InvalidateEntity(GraphEntityKind, u64),
113    PublishPath {
114        index: Text,
115        graph: Text,
116        definition: Text,
117    },
118}
119
120impl OwnedGraphMutation {
121    pub(super) fn retain(
122        value: GraphMutation<'_>,
123        control: &StorageReadControl,
124    ) -> VersionResult<Self> {
125        let text = |value: &str| {
126            let mut bytes = BudgetedVec::new(control.memory());
127            bytes.extend_from_slice(value.as_bytes())?;
128            Ok::<_, super::VersionError>(Arc::new(bytes))
129        };
130        Ok(match value {
131            GraphMutation::InvalidateGraph(graph) => Self::InvalidateGraph(text(graph)?),
132            GraphMutation::InvalidatePath(index) => Self::InvalidatePath(text(index)?),
133            GraphMutation::InvalidateEntity(kind, id) => Self::InvalidateEntity(kind, id),
134            GraphMutation::PublishPath {
135                index,
136                graph,
137                definition,
138            } => Self::PublishPath {
139                index: text(index)?,
140                graph: text(graph)?,
141                definition: text(definition)?,
142            },
143        })
144    }
145
146    pub(super) fn borrowed(&self) -> GraphMutation<'_> {
147        fn text(bytes: &Text) -> &str {
148            std::str::from_utf8(bytes).expect("retained graph text")
149        }
150        match self {
151            Self::InvalidateGraph(graph) => GraphMutation::InvalidateGraph(text(graph)),
152            Self::InvalidatePath(index) => GraphMutation::InvalidatePath(text(index)),
153            Self::InvalidateEntity(kind, id) => GraphMutation::InvalidateEntity(*kind, *id),
154            Self::PublishPath {
155                index,
156                graph,
157                definition,
158            } => GraphMutation::PublishPath {
159                index: text(index),
160                graph: text(graph),
161                definition: text(definition),
162            },
163        }
164    }
165}
166
167pub(super) struct GraphEffects {
168    pub(super) base: CommitSequence,
169    pub(super) operations: BudgetedVec<OwnedGraphMutation>,
170}
171
172pub(super) use resolve::resolve;