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