1use crate::cache::ManifestGeneration;
16
17pub trait GraphReadAllocation {
21 fn bytes(&self) -> usize;
22
23 fn grow(&mut self, bytes: usize) -> hawdb_core::Result<()>;
26
27 fn retain_state(&mut self) -> hawdb_core::Result<()> {
31 Ok(())
32 }
33}
34
35pub struct GraphReadAllocator<'a, T = Box<dyn GraphReadAllocation>> {
38 admit: &'a mut dyn FnMut(usize) -> hawdb_core::Result<T>,
39 work: GraphReadWork<'a>,
40 control: Option<&'a hawdb_core::RuntimeTaskContext>,
41}
42
43impl<'a, T> GraphReadAllocator<'a, T> {
44 pub fn new(
45 admit: &'a mut dyn FnMut(usize) -> hawdb_core::Result<T>,
46 task_context: Option<&'a hawdb_core::RuntimeTaskContext>,
47 ) -> Self {
48 Self {
49 admit,
50 work: GraphReadWork(task_context.filter(|task| task.has_work_limit())),
51 control: task_context,
52 }
53 }
54
55 pub fn reserve(&mut self, bytes: usize) -> hawdb_core::Result<T> {
56 (self.admit)(bytes)
57 }
58
59 pub(crate) fn work(&self) -> GraphReadWork<'a> {
60 self.work
61 }
62
63 pub(crate) fn checkpoint(&self) -> hawdb_core::Result<()> {
64 self.control.map_or(Ok(()), |task| {
65 task.checkpoint().map_err(|reason| {
66 hawdb_core::HawDBError::Execution(format!("runtime task stopped: {reason}"))
67 })
68 })
69 }
70
71 pub(crate) fn reborrow(&mut self) -> GraphReadAllocator<'_, T> {
72 GraphReadAllocator {
73 admit: self.admit,
74 work: self.work,
75 control: self.control,
76 }
77 }
78}
79
80#[derive(Clone, Copy)]
81pub(crate) struct GraphReadWork<'a>(Option<&'a hawdb_core::RuntimeTaskContext>);
82
83impl GraphReadWork<'_> {
84 pub(crate) fn has_work_limit(self) -> bool {
85 self.0.is_some()
86 }
87
88 pub(crate) fn charge_work(self, units: usize) -> hawdb_core::Result<()> {
89 self.0.map_or(Ok(()), |task| task.charge_work(units))
90 }
91}
92
93pub type ControlledGraphReadAllocator<'a> =
95 GraphReadAllocator<'a, Option<Box<dyn GraphReadAllocation>>>;
96
97pub enum AdmittedNodeRead {
100 Missing,
101 Stopped,
102 Node(AdmittedNodeRecord),
103}
104
105pub enum AdmittedRelationshipRead {
107 Missing,
108 Stopped,
109 Relationship(AdmittedRelationshipRecord),
110}
111
112pub struct AdmittedRelationshipRecord {
113 relationship: crate::RelRecord,
114 allocation: Box<dyn GraphReadAllocation>,
115}
116
117impl AdmittedRelationshipRecord {
118 pub(crate) fn new(
119 relationship: crate::RelRecord,
120 allocation: Box<dyn GraphReadAllocation>,
121 ) -> Self {
122 Self {
123 relationship,
124 allocation,
125 }
126 }
127
128 pub fn clone_admitted(
129 relationship: &crate::RelRecord,
130 allocation: Box<dyn GraphReadAllocation>,
131 ) -> hawdb_core::Result<Self> {
132 if allocation.bytes() < hawdb_core::ids::relationship_allocation_bytes(relationship) {
133 return Err(hawdb_core::HawDBError::Execution(
134 "relationship admission returned an insufficient allocation permit".into(),
135 ));
136 }
137 Ok(Self {
138 relationship: relationship.clone(),
139 allocation,
140 })
141 }
142
143 pub fn relationship(&self) -> &crate::RelRecord {
145 &self.relationship
146 }
147
148 pub fn into_parts(self) -> (crate::RelRecord, Box<dyn GraphReadAllocation>) {
149 (self.relationship, self.allocation)
150 }
151}
152
153pub struct AdmittedProjectedNode {
156 node: crate::ProjectedNodeRecord,
157 allocation: Box<dyn GraphReadAllocation>,
158}
159
160impl AdmittedProjectedNode {
161 pub(crate) fn new(
162 node: crate::ProjectedNodeRecord,
163 allocation: Box<dyn GraphReadAllocation>,
164 ) -> Self {
165 Self { node, allocation }
166 }
167
168 pub fn clone_admitted(
170 node: &crate::NodeRecord,
171 properties: &std::collections::BTreeSet<String>,
172 allocation: Box<dyn GraphReadAllocation>,
173 ) -> hawdb_core::Result<Self> {
174 if allocation.bytes() < hawdb_core::ids::projected_node_allocation_bytes(node, properties) {
175 return Err(hawdb_core::HawDBError::Execution(
176 "projected node admission returned an insufficient allocation permit".into(),
177 ));
178 }
179 Ok(Self {
180 node: hawdb_core::ids::project_node_record_ref(node, properties),
181 allocation,
182 })
183 }
184
185 pub fn into_parts(self) -> (crate::ProjectedNodeRecord, Box<dyn GraphReadAllocation>) {
186 (self.node, self.allocation)
187 }
188}
189
190pub struct AdmittedNodeRecord {
192 node: crate::NodeRecord,
193 allocation: Box<dyn GraphReadAllocation>,
194}
195
196impl AdmittedNodeRecord {
197 pub(crate) fn new(node: crate::NodeRecord, allocation: Box<dyn GraphReadAllocation>) -> Self {
198 Self { node, allocation }
199 }
200
201 pub fn clone_admitted(
204 node: &crate::NodeRecord,
205 allocation: Box<dyn GraphReadAllocation>,
206 ) -> hawdb_core::Result<Self> {
207 if allocation.bytes() < hawdb_core::ids::node_allocation_bytes(node) {
208 return Err(hawdb_core::HawDBError::Execution(
209 "full node admission returned an insufficient allocation permit".into(),
210 ));
211 }
212 Ok(Self {
213 node: node.clone(),
214 allocation,
215 })
216 }
217
218 pub(crate) fn from_full_projection(input: AdmittedProjectedNode) -> Self {
219 let (node, allocation) = input.into_parts();
220 Self {
221 node: crate::NodeRecord {
222 id: node.id,
223 labels: node.labels,
224 properties: node.properties,
225 },
226 allocation,
227 }
228 }
229
230 pub fn into_parts(self) -> (crate::NodeRecord, Box<dyn GraphReadAllocation>) {
231 (self.node, self.allocation)
232 }
233}
234
235pub struct GraphReadAdmission<'a> {
237 admit: std::cell::RefCell<
238 &'a mut dyn FnMut(usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>>,
239 >,
240}
241
242impl<'a> GraphReadAdmission<'a> {
243 pub fn new(
244 admit: &'a mut dyn FnMut(usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>>,
245 ) -> Self {
246 Self {
247 admit: std::cell::RefCell::new(admit),
248 }
249 }
250
251 pub fn reserve(&self, bytes: usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>> {
252 let allocation = (self.admit.borrow_mut())(bytes)?;
253 if allocation.bytes() < bytes {
254 return Err(hawdb_core::HawDBError::Execution(
255 "graph read admission returned an insufficient allocation permit".into(),
256 ));
257 }
258 Ok(allocation)
259 }
260}
261
262pub struct AdmittedKeySet<T: Ord> {
266 keys: std::collections::BTreeSet<T>,
267 allocation: Box<dyn GraphReadAllocation>,
268}
269
270impl<T: Ord> AdmittedKeySet<T> {
271 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
272 Ok(Self::with_allocation(admission.reserve(0)?))
273 }
274
275 pub(crate) fn with_allocation(allocation: Box<dyn GraphReadAllocation>) -> Self {
276 Self {
277 keys: std::collections::BTreeSet::new(),
278 allocation,
279 }
280 }
281
282 pub fn try_insert(&mut self, key: T) -> hawdb_core::Result<bool> {
283 self.allocation.grow(0)?;
286 if self.keys.contains(&key) {
287 return Ok(false);
288 }
289 self.allocation.retain_state()?;
290 let key_bytes = std::mem::size_of::<T>();
294 let bytes = self
295 .keys
296 .len()
297 .saturating_add(1)
298 .saturating_mul(64usize.saturating_add(key_bytes.saturating_mul(2)))
299 .saturating_add(128usize.saturating_add(key_bytes.saturating_mul(11)));
300 let additional = bytes.saturating_sub(self.allocation.bytes());
301 self.allocation.grow(additional)?;
302 if self.allocation.bytes() < bytes {
303 return Err(hawdb_core::HawDBError::Execution(
304 "graph read admission did not grow its allocation permit".into(),
305 ));
306 }
307 Ok(self.keys.insert(key))
308 }
309
310 pub fn try_extend(&mut self, keys: impl IntoIterator<Item = T>) -> hawdb_core::Result<()> {
311 for key in keys {
312 self.try_insert(key)?;
313 }
314 Ok(())
315 }
316}
317
318impl<T: Ord> std::ops::Deref for AdmittedKeySet<T> {
319 type Target = std::collections::BTreeSet<T>;
320 fn deref(&self) -> &Self::Target {
321 &self.keys
322 }
323}
324
325pub struct AdmittedVec<T> {
328 values: Vec<T>,
329 allocation: Box<dyn GraphReadAllocation>,
330}
331
332impl<T> AdmittedVec<T> {
333 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
334 Ok(Self {
335 values: Vec::new(),
336 allocation: admission.reserve(0)?,
337 })
338 }
339
340 pub fn try_push(&mut self, value: T) -> hawdb_core::Result<()> {
341 if self.values.len() == self.values.capacity() {
342 let capacity = self.values.capacity().saturating_mul(2).max(4);
343 let bytes = capacity.saturating_mul(std::mem::size_of::<T>());
344 self.allocation
345 .grow(bytes.saturating_sub(self.allocation.bytes()))?;
346 if self.allocation.bytes() < bytes {
347 return Err(hawdb_core::HawDBError::Execution(
348 "graph read admission did not grow its allocation permit".into(),
349 ));
350 }
351 self.values.reserve_exact(capacity - self.values.len());
352 }
353 self.values.push(value);
354 Ok(())
355 }
356
357 pub fn into_parts(self) -> (Vec<T>, Box<dyn GraphReadAllocation>) {
358 (self.values, self.allocation)
359 }
360
361 pub fn as_slice(&self) -> &[T] {
362 &self.values
363 }
364
365 pub fn as_mut_slice(&mut self) -> &mut [T] {
367 &mut self.values
368 }
369
370 pub fn clear(&mut self) {
371 self.values.clear();
373 }
374
375 pub fn swap_remove(&mut self, index: usize) -> T {
377 self.values.swap_remove(index)
378 }
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq)]
387pub struct PublishedReadView {
388 visible_commit_epoch: u64,
389 checkpoint_commit_epoch: Option<u64>,
390 physical_generation: Option<ManifestGeneration>,
391}
392
393impl PublishedReadView {
394 #[doc(hidden)]
395 pub fn new(
396 visible_commit_epoch: u64,
397 checkpoint_commit_epoch: Option<u64>,
398 physical_generation: Option<ManifestGeneration>,
399 ) -> Self {
400 debug_assert_eq!(
401 checkpoint_commit_epoch.is_some(),
402 physical_generation.is_some(),
403 "checkpoint epoch and physical generation must be published together"
404 );
405 debug_assert!(
406 checkpoint_commit_epoch.is_none_or(|epoch| epoch <= visible_commit_epoch),
407 "checkpoint commit epoch must not exceed the visible commit epoch"
408 );
409 Self {
410 visible_commit_epoch,
411 checkpoint_commit_epoch,
412 physical_generation,
413 }
414 }
415
416 pub const fn visible_commit_epoch(self) -> u64 {
417 self.visible_commit_epoch
418 }
419
420 pub const fn checkpoint_commit_epoch(self) -> Option<u64> {
421 self.checkpoint_commit_epoch
422 }
423
424 pub const fn physical_generation(self) -> Option<ManifestGeneration> {
425 self.physical_generation
426 }
427
428 pub const fn physical_base_is_current(self) -> bool {
429 matches!(
430 self.checkpoint_commit_epoch,
431 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch == self.visible_commit_epoch
432 )
433 }
434
435 pub const fn has_delta_after_physical_generation(self) -> bool {
436 matches!(
437 self.checkpoint_commit_epoch,
438 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch < self.visible_commit_epoch
439 )
440 }
441}