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 all_links(&self) -> Vec<GenericLink<u32>> {
66                self.all().into_iter().copied().collect()
67            }
68
69            fn query_links(
70                &self,
71                index: Option<u32>,
72                source: Option<u32>,
73                target: Option<u32>,
74            ) -> Vec<GenericLink<u32>> {
75                self.query(index, source, target)
76                    .into_iter()
77                    .copied()
78                    .collect()
79            }
80
81            fn search_link(&self, source: u32, target: u32) -> Option<u32> {
82                self.search(source, target)
83            }
84
85            fn get_or_create_link(&mut self, source: u32, target: u32) -> Result<u32, LinkError> {
86                Ok(self.get_or_create(source, target))
87            }
88
89            fn flush(&mut self) -> Result<(), LinkError> {
90                #[allow(clippy::redundant_closure_call)]
91                $flush(self)
92            }
93
94            fn has_external_changes(&self) -> Result<bool, LinkError> {
95                #[allow(clippy::redundant_closure_call)]
96                $external(self)
97            }
98
99            fn reload(&mut self) -> Result<(), LinkError> {
100                #[allow(clippy::redundant_closure_call)]
101                $reload(self)
102            }
103        }
104
105        impl LinksStorageRef<u32> for $type {
106            fn get_link_ref(&self, index: u32) -> Option<&Link> {
107                self.get(index)
108            }
109
110            fn all_link_refs(&self) -> Vec<&Link> {
111                self.all()
112            }
113
114            fn query_link_refs(
115                &self,
116                index: Option<u32>,
117                source: Option<u32>,
118                target: Option<u32>,
119            ) -> Vec<&Link> {
120                self.query(index, source, target)
121            }
122        }
123    };
124}
125
126impl_in_memory_links_storage!(
127    LinkStorage,
128    |storage: &mut LinkStorage, source, target| storage.create(source, target),
129    |storage: &mut LinkStorage| {
130        storage.save().map_err(storage_error)?;
131        storage.refresh_observed_revision()
132    },
133    |storage: &LinkStorage| {
134        Ok(StorageRevision::of(storage.database_path())? != storage.observed_revision())
135    },
136    |storage: &mut LinkStorage| storage.reload_from_disk().map_err(storage_error)
137);
138
139impl_in_memory_links_storage!(
140    PinnedTypesDecorator,
141    |storage: &mut PinnedTypesDecorator, source, target| storage.create(source, target),
142    |storage: &mut PinnedTypesDecorator| {
143        storage.save().map_err(storage_error)?;
144        storage.links_mut().refresh_observed_revision()
145    },
146    |storage: &PinnedTypesDecorator| storage.links().has_external_changes(),
147    |storage: &mut PinnedTypesDecorator| storage.links_mut().reload()
148);
149
150impl_in_memory_links_storage!(
151    NamedTypesDecorator,
152    |storage: &mut NamedTypesDecorator, source, target| storage.create(source, target),
153    |storage: &mut NamedTypesDecorator| {
154        storage.save().map_err(storage_error)?;
155        storage.links_mut().refresh_observed_revision()?;
156        storage.names_links_mut().refresh_observed_revision()
157    },
158    |storage: &NamedTypesDecorator| {
159        Ok(storage.links().has_external_changes()?
160            || storage.names_links().has_external_changes()?)
161    },
162    |storage: &mut NamedTypesDecorator| {
163        storage.links_mut().reload()?;
164        storage.names_links_mut().reload()
165    }
166);
167
168/// Compile-time proof that the address type is not hard-coded to `u32`:
169/// the generic bound below accepts any `doublets` address type.
170#[allow(dead_code)]
171fn assert_generic_over_address<T: LinkReference, S: LinksStorage<T>>(_storage: &S) {}