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 check_work_limit(self) -> hawdb_core::Result<()> {
89 self.0.map_or(Ok(()), |task| task.check_work_limit())
90 }
91
92 pub(crate) fn charge_work(self, units: usize) -> hawdb_core::Result<()> {
93 self.0.map_or(Ok(()), |task| task.charge_work(units))
94 }
95}
96
97pub type ControlledGraphReadAllocator<'a> =
99 GraphReadAllocator<'a, Option<Box<dyn GraphReadAllocation>>>;
100
101pub enum AdmittedNodeRead {
104 Missing,
105 Stopped,
106 Node(AdmittedNodeRecord),
107}
108
109pub enum AdmittedRelationshipRead {
111 Missing,
112 Stopped,
113 Relationship(AdmittedRelationshipRecord),
114}
115
116pub struct AdmittedRelationshipRecord {
117 relationship: crate::RelRecord,
118 allocation: Box<dyn GraphReadAllocation>,
119}
120
121impl AdmittedRelationshipRecord {
122 pub(crate) fn new(
123 relationship: crate::RelRecord,
124 allocation: Box<dyn GraphReadAllocation>,
125 ) -> Self {
126 Self {
127 relationship,
128 allocation,
129 }
130 }
131
132 pub fn clone_admitted(
133 relationship: &crate::RelRecord,
134 allocation: Box<dyn GraphReadAllocation>,
135 ) -> hawdb_core::Result<Self> {
136 if allocation.bytes() < hawdb_core::ids::relationship_allocation_bytes(relationship) {
137 return Err(hawdb_core::HawDBError::Execution(
138 "relationship admission returned an insufficient allocation permit".into(),
139 ));
140 }
141 Ok(Self {
142 relationship: relationship.clone(),
143 allocation,
144 })
145 }
146
147 pub fn relationship(&self) -> &crate::RelRecord {
149 &self.relationship
150 }
151
152 pub fn retain_properties(&mut self, mut keep: impl FnMut(&str) -> bool) {
155 self.relationship
156 .properties
157 .retain(|name, _| keep(name.as_str()));
158 }
159
160 pub fn into_parts(self) -> (crate::RelRecord, Box<dyn GraphReadAllocation>) {
161 (self.relationship, self.allocation)
162 }
163}
164
165pub struct AdmittedProjectedNode {
168 node: crate::ProjectedNodeRecord,
169 allocation: Box<dyn GraphReadAllocation>,
170}
171
172impl AdmittedProjectedNode {
173 pub(crate) fn new(
174 node: crate::ProjectedNodeRecord,
175 allocation: Box<dyn GraphReadAllocation>,
176 ) -> Self {
177 Self { node, allocation }
178 }
179
180 pub fn clone_admitted(
182 node: &crate::NodeRecord,
183 properties: &std::collections::BTreeSet<String>,
184 allocation: Box<dyn GraphReadAllocation>,
185 ) -> hawdb_core::Result<Self> {
186 if allocation.bytes() < hawdb_core::ids::projected_node_allocation_bytes(node, properties) {
187 return Err(hawdb_core::HawDBError::Execution(
188 "projected node admission returned an insufficient allocation permit".into(),
189 ));
190 }
191 Ok(Self {
192 node: hawdb_core::ids::project_node_record_ref(node, properties),
193 allocation,
194 })
195 }
196
197 pub fn into_parts(self) -> (crate::ProjectedNodeRecord, Box<dyn GraphReadAllocation>) {
198 (self.node, self.allocation)
199 }
200}
201
202pub struct AdmittedNodeRecord {
204 node: crate::NodeRecord,
205 allocation: Box<dyn GraphReadAllocation>,
206}
207
208impl AdmittedNodeRecord {
209 pub(crate) fn new(node: crate::NodeRecord, allocation: Box<dyn GraphReadAllocation>) -> Self {
210 Self { node, allocation }
211 }
212
213 pub fn clone_admitted(
216 node: &crate::NodeRecord,
217 allocation: Box<dyn GraphReadAllocation>,
218 ) -> hawdb_core::Result<Self> {
219 if allocation.bytes() < hawdb_core::ids::node_allocation_bytes(node) {
220 return Err(hawdb_core::HawDBError::Execution(
221 "full node admission returned an insufficient allocation permit".into(),
222 ));
223 }
224 Ok(Self {
225 node: node.clone(),
226 allocation,
227 })
228 }
229
230 pub(crate) fn from_full_projection(input: AdmittedProjectedNode) -> Self {
231 let (node, allocation) = input.into_parts();
232 Self {
233 node: crate::NodeRecord {
234 id: node.id,
235 labels: node.labels,
236 properties: node.properties,
237 },
238 allocation,
239 }
240 }
241
242 pub fn into_parts(self) -> (crate::NodeRecord, Box<dyn GraphReadAllocation>) {
243 (self.node, self.allocation)
244 }
245}
246
247pub struct GraphReadAdmission<'a> {
249 admit: std::cell::RefCell<
250 &'a mut dyn FnMut(usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>>,
251 >,
252}
253
254impl<'a> GraphReadAdmission<'a> {
255 pub fn new(
256 admit: &'a mut dyn FnMut(usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>>,
257 ) -> Self {
258 Self {
259 admit: std::cell::RefCell::new(admit),
260 }
261 }
262
263 pub fn reserve(&self, bytes: usize) -> hawdb_core::Result<Box<dyn GraphReadAllocation>> {
264 let allocation = (self.admit.borrow_mut())(bytes)?;
265 if allocation.bytes() < bytes {
266 return Err(hawdb_core::HawDBError::Execution(
267 "graph read admission returned an insufficient allocation permit".into(),
268 ));
269 }
270 Ok(allocation)
271 }
272}
273
274pub struct AdmittedKeySet<T: Ord> {
278 keys: std::collections::BTreeSet<T>,
279 allocation: Box<dyn GraphReadAllocation>,
280}
281
282impl<T: Ord> AdmittedKeySet<T> {
283 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
284 Ok(Self::with_allocation(admission.reserve(0)?))
285 }
286
287 pub(crate) fn with_allocation(allocation: Box<dyn GraphReadAllocation>) -> Self {
288 Self {
289 keys: std::collections::BTreeSet::new(),
290 allocation,
291 }
292 }
293
294 pub fn try_insert(&mut self, key: T) -> hawdb_core::Result<bool> {
295 self.allocation.grow(0)?;
298 if self.keys.contains(&key) {
299 return Ok(false);
300 }
301 self.allocation.retain_state()?;
302 let key_bytes = std::mem::size_of::<T>();
306 let bytes = self
307 .keys
308 .len()
309 .saturating_add(1)
310 .saturating_mul(64usize.saturating_add(key_bytes.saturating_mul(2)))
311 .saturating_add(128usize.saturating_add(key_bytes.saturating_mul(11)));
312 let additional = bytes.saturating_sub(self.allocation.bytes());
313 self.allocation.grow(additional)?;
314 if self.allocation.bytes() < bytes {
315 return Err(hawdb_core::HawDBError::Execution(
316 "graph read admission did not grow its allocation permit".into(),
317 ));
318 }
319 Ok(self.keys.insert(key))
320 }
321
322 pub fn try_extend(&mut self, keys: impl IntoIterator<Item = T>) -> hawdb_core::Result<()> {
323 for key in keys {
324 self.try_insert(key)?;
325 }
326 Ok(())
327 }
328}
329
330impl<T: Ord> std::ops::Deref for AdmittedKeySet<T> {
331 type Target = std::collections::BTreeSet<T>;
332 fn deref(&self) -> &Self::Target {
333 &self.keys
334 }
335}
336
337pub struct AdmittedVec<T> {
340 values: Vec<T>,
341 allocation: Box<dyn GraphReadAllocation>,
342}
343
344impl<T> AdmittedVec<T> {
345 pub fn new(admission: &GraphReadAdmission<'_>) -> hawdb_core::Result<Self> {
346 Ok(Self {
347 values: Vec::new(),
348 allocation: admission.reserve(0)?,
349 })
350 }
351
352 pub fn try_push(&mut self, value: T) -> hawdb_core::Result<()> {
353 if self.values.len() == self.values.capacity() {
354 let capacity = self.values.capacity().saturating_mul(2).max(4);
355 let bytes = capacity.saturating_mul(std::mem::size_of::<T>());
356 self.allocation
357 .grow(bytes.saturating_sub(self.allocation.bytes()))?;
358 if self.allocation.bytes() < bytes {
359 return Err(hawdb_core::HawDBError::Execution(
360 "graph read admission did not grow its allocation permit".into(),
361 ));
362 }
363 self.values.reserve_exact(capacity - self.values.len());
364 }
365 self.values.push(value);
366 Ok(())
367 }
368
369 pub fn into_parts(self) -> (Vec<T>, Box<dyn GraphReadAllocation>) {
370 (self.values, self.allocation)
371 }
372
373 pub fn as_slice(&self) -> &[T] {
374 &self.values
375 }
376
377 pub fn as_mut_slice(&mut self) -> &mut [T] {
379 &mut self.values
380 }
381
382 pub fn clear(&mut self) {
383 self.values.clear();
385 }
386
387 pub fn swap_remove(&mut self, index: usize) -> T {
389 self.values.swap_remove(index)
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub struct PublishedReadView {
400 visible_commit_epoch: u64,
401 checkpoint_commit_epoch: Option<u64>,
402 physical_generation: Option<ManifestGeneration>,
403}
404
405impl PublishedReadView {
406 #[doc(hidden)]
407 pub fn new(
408 visible_commit_epoch: u64,
409 checkpoint_commit_epoch: Option<u64>,
410 physical_generation: Option<ManifestGeneration>,
411 ) -> Self {
412 debug_assert_eq!(
413 checkpoint_commit_epoch.is_some(),
414 physical_generation.is_some(),
415 "checkpoint epoch and physical generation must be published together"
416 );
417 debug_assert!(
418 checkpoint_commit_epoch.is_none_or(|epoch| epoch <= visible_commit_epoch),
419 "checkpoint commit epoch must not exceed the visible commit epoch"
420 );
421 Self {
422 visible_commit_epoch,
423 checkpoint_commit_epoch,
424 physical_generation,
425 }
426 }
427
428 pub const fn visible_commit_epoch(self) -> u64 {
429 self.visible_commit_epoch
430 }
431
432 pub const fn checkpoint_commit_epoch(self) -> Option<u64> {
433 self.checkpoint_commit_epoch
434 }
435
436 pub const fn physical_generation(self) -> Option<ManifestGeneration> {
437 self.physical_generation
438 }
439
440 pub const fn physical_base_is_current(self) -> bool {
441 matches!(
442 self.checkpoint_commit_epoch,
443 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch == self.visible_commit_epoch
444 )
445 }
446
447 pub const fn has_delta_after_physical_generation(self) -> bool {
448 matches!(
449 self.checkpoint_commit_epoch,
450 Some(checkpoint_commit_epoch) if checkpoint_commit_epoch < self.visible_commit_epoch
451 )
452 }
453}