Skip to main content

link_cli/storage/
decorator_impls.rs

1//! [`LinksStorage`] implementations for the CLI's in-memory decorators.
2//!
3//! These make [`LinkStorage`], [`PinnedTypesDecorator`] and
4//! [`NamedTypesDecorator`] usable anywhere the generic transactions layer
5//! expects a storage, without changing their existing inherent API.
6//!
7//! # Durability
8//!
9//! All three keep their links in memory and rewrite the whole database
10//! file on [`LinksStorage::flush`], so **`flush` (or the inherent `save`)
11//! is required for durability**: nothing reaches the disk before it. The
12//! rewrite truncates the existing file in place rather than renaming a
13//! temporary over it, so the inode is preserved and other processes that
14//! already opened the database keep pointing at the same file.
15
16use doublets::data::LinkReference;
17
18use crate::error::LinkError;
19use crate::link::{GenericLink, Link};
20use crate::link_storage::LinkStorage;
21use crate::named_types::NamedTypesDecorator;
22use crate::pinned_types::PinnedTypesDecorator;
23use crate::storage::traits::{LinksStorage, LinksStorageRef, StorageRevision};
24
25fn storage_error(error: anyhow::Error) -> LinkError {
26    LinkError::StorageError(format!("{error:#}"))
27}
28
29/// Generates the delegating [`LinksStorage`]/[`LinksStorageRef`] impls
30/// shared by the three in-memory decorators, which expose the same
31/// inherent method set.
32macro_rules! impl_in_memory_links_storage {
33    ($type:ty, $create:expr, $flush:expr, $external:expr, $reload:expr) => {
34        impl LinksStorage<u32> for $type {
35            fn create_link(&mut self, source: u32, target: u32) -> Result<u32, LinkError> {
36                #[allow(clippy::redundant_closure_call)]
37                Ok($create(self, source, target))
38            }
39
40            fn ensure_link_created(&mut self, index: u32) -> Result<u32, LinkError> {
41                Ok(self.ensure_created(index))
42            }
43
44            fn get_link(&self, index: u32) -> Option<GenericLink<u32>> {
45                self.get(index).copied()
46            }
47
48            fn link_exists(&self, index: u32) -> bool {
49                self.exists(index)
50            }
51
52            fn update_link(
53                &mut self,
54                index: u32,
55                source: u32,
56                target: u32,
57            ) -> Result<GenericLink<u32>, LinkError> {
58                self.update(index, source, target).map_err(storage_error)
59            }
60
61            fn delete_link(&mut self, index: u32) -> Result<GenericLink<u32>, LinkError> {
62                self.delete(index).map_err(storage_error)
63            }
64
65            fn update_link_observed(
66                &mut self,
67                index: u32,
68                source: u32,
69                target: u32,
70                observer: &mut dyn FnMut(GenericLink<u32>, GenericLink<u32>),
71            ) -> Result<GenericLink<u32>, LinkError> {
72                self.update_observed(index, source, target, observer)
73                    .map_err(storage_error)
74            }
75
76            fn delete_link_observed(
77                &mut self,
78                index: u32,
79                observer: &mut dyn FnMut(GenericLink<u32>, GenericLink<u32>),
80            ) -> Result<GenericLink<u32>, LinkError> {
81                self.delete_observed(index, observer).map_err(storage_error)
82            }
83
84            fn all_links(&self) -> Vec<GenericLink<u32>> {
85                self.all().into_iter().copied().collect()
86            }
87
88            fn query_links(
89                &self,
90                index: Option<u32>,
91                source: Option<u32>,
92                target: Option<u32>,
93            ) -> Vec<GenericLink<u32>> {
94                self.query(index, source, target)
95                    .into_iter()
96                    .copied()
97                    .collect()
98            }
99
100            fn search_link(&self, source: u32, target: u32) -> Option<u32> {
101                self.search(source, target)
102            }
103
104            fn get_or_create_link(&mut self, source: u32, target: u32) -> Result<u32, LinkError> {
105                Ok(self.get_or_create(source, target))
106            }
107
108            fn flush(&mut self) -> Result<(), LinkError> {
109                #[allow(clippy::redundant_closure_call)]
110                $flush(self)
111            }
112
113            fn has_external_changes(&self) -> Result<bool, LinkError> {
114                #[allow(clippy::redundant_closure_call)]
115                $external(self)
116            }
117
118            fn reload(&mut self) -> Result<(), LinkError> {
119                #[allow(clippy::redundant_closure_call)]
120                $reload(self)
121            }
122        }
123
124        impl LinksStorageRef<u32> for $type {
125            fn get_link_ref(&self, index: u32) -> Option<&Link> {
126                self.get(index)
127            }
128
129            fn all_link_refs(&self) -> Vec<&Link> {
130                self.all()
131            }
132
133            fn query_link_refs(
134                &self,
135                index: Option<u32>,
136                source: Option<u32>,
137                target: Option<u32>,
138            ) -> Vec<&Link> {
139                self.query(index, source, target)
140            }
141        }
142    };
143}
144
145impl_in_memory_links_storage!(
146    LinkStorage,
147    |storage: &mut LinkStorage, source, target| storage.create(source, target),
148    |storage: &mut LinkStorage| {
149        storage.save().map_err(storage_error)?;
150        storage.refresh_observed_revision()
151    },
152    |storage: &LinkStorage| {
153        Ok(StorageRevision::of(storage.database_path())? != storage.observed_revision())
154    },
155    |storage: &mut LinkStorage| storage.reload_from_disk().map_err(storage_error)
156);
157
158impl_in_memory_links_storage!(
159    PinnedTypesDecorator,
160    |storage: &mut PinnedTypesDecorator, source, target| storage.create(source, target),
161    |storage: &mut PinnedTypesDecorator| {
162        storage.save().map_err(storage_error)?;
163        storage.links_mut().refresh_observed_revision()
164    },
165    |storage: &PinnedTypesDecorator| storage.links().has_external_changes(),
166    |storage: &mut PinnedTypesDecorator| storage.links_mut().reload()
167);
168
169impl_in_memory_links_storage!(
170    NamedTypesDecorator,
171    |storage: &mut NamedTypesDecorator, source, target| storage.create(source, target),
172    |storage: &mut NamedTypesDecorator| {
173        storage.save().map_err(storage_error)?;
174        storage.links_mut().refresh_observed_revision()?;
175        storage.names_links_mut().refresh_observed_revision()
176    },
177    |storage: &NamedTypesDecorator| {
178        Ok(storage.links().has_external_changes()?
179            || storage.names_links().has_external_changes()?)
180    },
181    |storage: &mut NamedTypesDecorator| {
182        storage.links_mut().reload()?;
183        storage.names_links_mut().reload()
184    }
185);
186
187/// Compile-time proof that the address type is not hard-coded to `u32`:
188/// the generic bound below accepts any `doublets` address type.
189#[allow(dead_code)]
190fn assert_generic_over_address<T: LinkReference, S: LinksStorage<T>>(_storage: &S) {}