1use crate::cache::ManifestGeneration;
16
17pub trait GraphReadAllocation {
21 fn bytes(&self) -> usize;
22 fn grow(&mut self, bytes: usize) -> hawdb_core::Result<()>;
25
26 fn retain_state(&mut self) -> hawdb_core::Result<()> {
30 Ok(())
31 }
32}
33
34pub type GraphReadAllocator<'a> =
35 dyn FnMut(usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>> + 'a;
36
37pub type ControlledGraphReadAllocator<'a> =
39 dyn FnMut(usize) -> hawdb_core::Result<Option<Box<dyn GraphReadAllocation>>> + 'a;
40
41pub enum AdmittedNodeRead {
44 Missing,
45 Stopped,
46 Node(AdmittedNodeRecord),
47}
48
49pub enum AdmittedRelationshipRead {
51 Missing,
52 Stopped,
53 Relationship(AdmittedRelationshipRecord),
54}
55
56pub struct AdmittedRelationshipRecord {
57 relationship: crate::RelRecord,
58 allocation: Box<dyn GraphReadAllocation>,
59}
60
61impl AdmittedRelationshipRecord {
62 pub(crate) fn new(
63 relationship: crate::RelRecord,
64 allocation: Box<dyn GraphReadAllocation>,
65 ) -> Self {
66 Self {
67 relationship,
68 allocation,
69 }
70 }
71
72 pub fn clone_admitted(
73 relationship: &crate::RelRecord,
74 allocation: Box<dyn GraphReadAllocation>,
75 ) -> hawdb_core::Result<Self> {
76 if allocation.bytes() < hawdb_core::ids::relationship_allocation_bytes(relationship) {
77 return Err(hawdb_core::HawDBError::Execution(
78 "relationship admission returned an insufficient allocation permit".into(),
79 ));
80 }
81 Ok(Self {
82 relationship: relationship.clone(),
83 allocation,
84 })
85 }
86
87 pub fn relationship(&self) -> &crate::RelRecord {
89 &self.relationship
90 }
91
92 pub fn into_parts(self) -> (crate::RelRecord, Box<dyn GraphReadAllocation>) {
93 (self.relationship, self.allocation)
94 }
95}
96
97pub struct AdmittedProjectedNode {
100 node: crate::ProjectedNodeRecord,
101 allocation: Box<dyn GraphReadAllocation>,
102}
103
104impl AdmittedProjectedNode {
105 pub(crate) fn new(
106 node: crate::ProjectedNodeRecord,
107 allocation: Box<dyn GraphReadAllocation>,
108 ) -> Self {
109 Self { node, allocation }
110 }
111
112 pub fn clone_admitted(
114 node: &crate::NodeRecord,
115 properties: &std::collections::BTreeSet<String>,
116 allocation: Box<dyn GraphReadAllocation>,
117 ) -> hawdb_core::Result<Self> {
118 if allocation.bytes() < hawdb_core::ids::projected_node_allocation_bytes(node, properties) {
119 return Err(hawdb_core::HawDBError::Execution(
120 "projected node admission returned an insufficient allocation permit".into(),
121 ));
122 }
123 Ok(Self {
124 node: hawdb_core::ids::project_node_record_ref(node, properties),
125 allocation,
126 })
127 }
128
129 pub fn into_parts(self) -> (crate::ProjectedNodeRecord, Box<dyn GraphReadAllocation>) {
130 (self.node, self.allocation)
131 }
132}
133
134pub struct AdmittedNodeRecord {
136 node: crate::NodeRecord,
137 allocation: Box<dyn GraphReadAllocation>,
138}
139
140impl AdmittedNodeRecord {
141 pub(crate) fn new(node: crate::NodeRecord, allocation: Box<dyn GraphReadAllocation>) -> Self {
142 Self { node, allocation }
143 }
144
145 pub fn clone_admitted(
148 node: &crate::NodeRecord,
149 allocation: Box<dyn GraphReadAllocation>,
150 ) -> hawdb_core::Result<Self> {
151 if allocation.bytes() < hawdb_core::ids::node_allocation_bytes(node) {
152 return Err(hawdb_core::HawDBError::Execution(
153 "full node admission returned an insufficient allocation permit".into(),
154 ));
155 }
156 Ok(Self {
157 node: node.clone(),
158 allocation,
159 })
160 }
161
162 pub(crate) fn from_full_projection(input: AdmittedProjectedNode) -> Self {
163 let (node, allocation) = input.into_parts();
164 Self {
165 node: crate::NodeRecord {
166 id: node.id,
167 labels: node.labels,
168 properties: node.properties,
169 },
170 allocation,
171 }
172 }
173
174 pub fn into_parts(self) -> (crate::NodeRecord, Box<dyn GraphReadAllocation>) {
175 (self.node, self.allocation)
176 }
177}
178
179pub struct GraphReadAdmission<'a> {
181 admit: std::cell::RefCell<&'a mut GraphReadAllocator<'a>>,
182}
183
184impl<'a> GraphReadAdmission<'a> {
185 pub fn new(admit: &'a mut GraphReadAllocator<'a>) -> Self {
186 Self {
187 admit: std::cell::RefCell::new(admit),
188 }
189 }
190
191 pub fn reserve(&self, bytes: usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>> {
192 let allocation = (self.admit.borrow_mut())(bytes)?;
193 if allocation.bytes() < bytes {
194 return Err(hawdb_core::HawDBError::Execution(
195 "graph read admission returned an insufficient allocation permit".into(),
196 ));
197 }
198 Ok(allocation)
199 }
200}
201
202pub struct AdmittedKeySet<T: Ord> {
206 keys: std::collections::BTreeSet<T>,
207 allocation: Box<dyn GraphReadAllocation>,
208}
209
210impl<T: Ord> AdmittedKeySet<T> {
211 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
212 Ok(Self::with_allocation(admission.reserve(0)?))
213 }
214
215 pub(crate) fn with_allocation(allocation: Box<dyn GraphReadAllocation>) -> Self {
216 Self {
217 keys: std::collections::BTreeSet::new(),
218 allocation,
219 }
220 }
221
222 pub fn try_insert(&mut self, key: T) -> hawdb_core::Result<bool> {
223 self.allocation.grow(0)?;
226 if self.keys.contains(&key) {
227 return Ok(false);
228 }
229 self.allocation.retain_state()?;
230 let key_bytes = std::mem::size_of::<T>();
234 let bytes = self
235 .keys
236 .len()
237 .saturating_add(1)
238 .saturating_mul(64usize.saturating_add(key_bytes.saturating_mul(2)))
239 .saturating_add(128usize.saturating_add(key_bytes.saturating_mul(11)));
240 let additional = bytes.saturating_sub(self.allocation.bytes());
241 self.allocation.grow(additional)?;
242 if self.allocation.bytes() < bytes {
243 return Err(hawdb_core::HawDBError::Execution(
244 "graph read admission did not grow its allocation permit".into(),
245 ));
246 }
247 Ok(self.keys.insert(key))
248 }
249
250 pub fn try_extend(&mut self, keys: impl IntoIterator<Item = T>) -> hawdb_core::Result<()> {
251 for key in keys {
252 self.try_insert(key)?;
253 }
254 Ok(())
255 }
256}
257
258impl<T: Ord> std::ops::Deref for AdmittedKeySet<T> {
259 type Target = std::collections::BTreeSet<T>;
260 fn deref(&self) -> &Self::Target {
261 &self.keys
262 }
263}
264
265pub struct AdmittedVec<T> {
268 values: Vec<T>,
269 allocation: Box<dyn GraphReadAllocation>,
270}
271
272impl<T> AdmittedVec<T> {
273 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
274 Ok(Self {
275 values: Vec::new(),
276 allocation: admission.reserve(0)?,
277 })
278 }
279
280 pub fn try_push(&mut self, value: T) -> hawdb_core::Result<()> {
281 if self.values.len() == self.values.capacity() {
282 let capacity = self.values.capacity().saturating_mul(2).max(4);
283 let bytes = capacity.saturating_mul(std::mem::size_of::<T>());
284 self.allocation
285 .grow(bytes.saturating_sub(self.allocation.bytes()))?;
286 if self.allocation.bytes() < bytes {
287 return Err(hawdb_core::HawDBError::Execution(
288 "graph read admission did not grow its allocation permit".into(),
289 ));
290 }
291 self.values.reserve_exact(capacity - self.values.len());
292 }
293 self.values.push(value);
294 Ok(())
295 }
296
297 pub fn into_parts(self) -> (Vec<T>, Box<dyn GraphReadAllocation>) {
298 (self.values, self.allocation)
299 }
300
301 pub fn as_slice(&self) -> &[T] {
302 &self.values
303 }
304
305 pub fn as_mut_slice(&mut self) -> &mut [T] {
307 &mut self.values
308 }
309
310 pub fn clear(&mut self) {
311 self.values.clear();
313 }
314
315 pub fn swap_remove(&mut self, index: usize) -> T {
317 self.values.swap_remove(index)
318 }
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
327pub struct PublishedReadView {
328 visible_commit_epoch: u64,
329 checkpoint_commit_epoch: Option<u64>,
330 physical_generation: Option<ManifestGeneration>,
331}
332
333impl PublishedReadView {
334 #[doc(hidden)]
335 pub fn new(
336 visible_commit_epoch: u64,
337 checkpoint_commit_epoch: Option<u64>,
338 physical_generation: Option<ManifestGeneration>,
339 ) -> Self {
340 debug_assert_eq!(
341 checkpoint_commit_epoch.is_some(),
342 physical_generation.is_some(),
343 "checkpoint epoch and physical generation must be published together"
344 );
345 debug_assert!(
346 checkpoint_commit_epoch.is_none_or(|epoch| epoch <= visible_commit_epoch),
347 "checkpoint commit epoch must not exceed the visible commit epoch"
348 );
349 Self {
350 visible_commit_epoch,
351 checkpoint_commit_epoch,
352 physical_generation,
353 }
354 }
355
356 pub const fn visible_commit_epoch(self) -> u64 {
357 self.visible_commit_epoch
358 }
359
360 pub const fn checkpoint_commit_epoch(self) -> Option<u64> {
361 self.checkpoint_commit_epoch
362 }
363
364 pub const fn physical_generation(self) -> Option<ManifestGeneration> {
365 self.physical_generation
366 }
367
368 pub const fn physical_base_is_current(self) -> bool {
369 matches!(
370 self.checkpoint_commit_epoch,
371 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch == self.visible_commit_epoch
372 )
373 }
374
375 pub const fn has_delta_after_physical_generation(self) -> bool {
376 matches!(
377 self.checkpoint_commit_epoch,
378 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch < self.visible_commit_epoch
379 )
380 }
381}