link_cli/link_storage_doublets.rs
1//! Bridge between the CLI's [`LinkStorage`] and the upstream `doublets` traits.
2//!
3//! Implementing [`Links`] and [`Doublets`] for [`LinkStorage`] lets this crate
4//! reuse the `doublets::decorators` layer instead of re-implementing uniqueness
5//! resolution and cascading deletion by hand. It is the direct analogue of what
6//! the C# implementation does in
7//! `Foundation.Data.Doublets.Cli.NamedTypesDecorator.MakeLinks`:
8//!
9//! ```csharp
10//! var links = new UnitedMemoryLinks<TLinkAddress>(databaseFilename);
11//! return links.DecorateWithAutomaticUniquenessAndUsagesResolution();
12//! ```
13//!
14//! [`LinkStorage`] plays the `UnitedMemoryLinks` role — a plain store with no
15//! policy of its own — and
16//! [`DecoratorsExt::with_automatic_uniqueness_and_usages_resolution`](doublets::decorators::DecoratorsExt::with_automatic_uniqueness_and_usages_resolution)
17//! supplies the policy.
18//!
19//! The impls are also part of the public API on purpose: any embedder can now
20//! stack arbitrary upstream decorators (or its own) onto a [`LinkStorage`], and
21//! pass one anywhere a `doublets::Doublets<u32>` is expected.
22
23use std::sync::OnceLock;
24
25use doublets::data::{Flow, LinksConstants, ReadHandler, WriteHandler};
26use doublets::{Doublets, Error, Link as DoubletsLink, Links};
27
28use crate::link::Link;
29use crate::link_storage::LinkStorage;
30
31/// The [`LinksConstants`] every [`LinkStorage`] reports.
32///
33/// These are the *hybrid* constants, the direct analogue of C#'s
34/// `LinksConstants<TLinkAddress>` default for `Hybrid<uint>` addresses: `null`
35/// is `0`, internal addresses occupy the lower half of the `u32` range, and the
36/// upper half is reserved for external references.
37///
38/// The external half is not optional. A [`LinkStorage`] backing a names
39/// database stores exactly the values
40/// [`external_reference`](crate::hybrid_reference::external_reference)
41/// produces — `0 - value`, i.e. the top of the `u32` range — both for named
42/// links and for the raw character codes behind Unicode symbols. With the
43/// internal-only constants those values fall either inside `internal_range`,
44/// where
45/// [`ensure_inner_reference_exists`](doublets::decorators) would demand a
46/// stored link at that address, or exactly on a service constant: the external
47/// reference of link `4` is `0u32.wrapping_sub(4)`, which the internal-only
48/// constants define as `any`. Declaring the external range keeps every hybrid
49/// reference outside both.
50pub fn link_storage_constants() -> &'static LinksConstants<u32> {
51 static CONSTANTS: OnceLock<LinksConstants<u32>> = OnceLock::new();
52 CONSTANTS.get_or_init(LinksConstants::external)
53}
54
55fn as_doublets_link(link: &Link) -> DoubletsLink<u32> {
56 DoubletsLink::new(link.index, link.source, link.target)
57}
58
59/// Reads one part out of a raw query slice, defaulting to `null` when the slice
60/// is shorter — the same rule `doublets` uses internally.
61fn part(query: &[u32], index: usize) -> u32 {
62 query.get(index).copied().unwrap_or(0)
63}
64
65/// Every link, ordered by address.
66///
67/// Mirrors `each_core(handler, &[])` in the upstream unit store, which walks
68/// allocated addresses from `1` upwards.
69fn all(storage: &LinkStorage) -> Vec<Link> {
70 let mut matched: Vec<Link> = storage.all().into_iter().copied().collect();
71 matched.sort_by_key(|link| link.index);
72 matched
73}
74
75/// The `(any, source, target)` case, reproducing the *index* semantics of the
76/// upstream unit store rather than a plain scan.
77///
78/// In `doublets` a link is only reachable through the `(source, target)`
79/// lookups while it is attached to the source and target trees, and
80/// [`mem::united::Store::update_links`] only attaches the parts that are not
81/// `null`:
82///
83/// ```rust,ignore
84/// if place.source != T::from_byte(0) { unsafe { self.attach_source(index); } }
85/// if place.target != T::from_byte(0) { unsafe { self.attach_target(index); } }
86/// ```
87///
88/// A freshly created — or deliberately blanked — link therefore never answers a
89/// `(source, target)` query, which is exactly what keeps
90/// [`UniquenessResolver`](doublets::decorators::UniquenessResolver) from merging
91/// all the not-yet-filled links with each other. Matching that rule here is what
92/// makes the decorators behave the same on top of a [`LinkStorage`].
93fn by_pattern(storage: &LinkStorage, source: u32, target: u32) -> Vec<Link> {
94 let constants = link_storage_constants();
95 let (any, null) = (constants.any, constants.null);
96
97 match (source == any, target == any) {
98 (true, true) => all(storage),
99 // `targets.each_usages(target)`: the tree rooted at a null target is empty.
100 (true, false) if target == null => Vec::new(),
101 (true, false) => {
102 let mut matched: Vec<Link> = storage
103 .all()
104 .into_iter()
105 .filter(|link| link.target == target)
106 .copied()
107 .collect();
108 matched.sort_by_key(|link| link.index);
109 matched
110 }
111 // `sources.each_usages(source)`: the tree rooted at a null source is empty.
112 (false, true) if source == null => Vec::new(),
113 (false, true) => {
114 let mut matched: Vec<Link> = storage
115 .all()
116 .into_iter()
117 .filter(|link| link.source == source)
118 .copied()
119 .collect();
120 matched.sort_by_key(|link| link.index);
121 matched
122 }
123 (false, false) if source == null || target == null => Vec::new(),
124 // `sources.search(source, target)` yields at most one link; the lowest
125 // address wins so the answer never depends on hash map ordering.
126 (false, false) => storage
127 .all()
128 .into_iter()
129 .filter(|link| link.source == source && link.target == target)
130 .min_by_key(|link| link.index)
131 .copied()
132 .into_iter()
133 .collect(),
134 }
135}
136
137/// Links matching `query`, ordered by address so that every traversal (and
138/// therefore every cascade) is deterministic regardless of hash map ordering.
139///
140/// This is a faithful port of `each_core` in the upstream unit store, including
141/// its handling of one- and two-element queries and of the `any` constant.
142/// Query shapes the raw interface does not define match nothing.
143fn matching(storage: &LinkStorage, query: &[u32]) -> Vec<Link> {
144 let any = link_storage_constants().any;
145
146 match *query {
147 [] => all(storage),
148 [index] if index == any => all(storage),
149 [index] => storage.get(index).copied().into_iter().collect(),
150 [index, value] if index == any && value == any => all(storage),
151 [index, value] if index == any => {
152 // Upstream unions the two usage trees without deduplicating, so a
153 // link that both starts and ends at `value` is visited twice.
154 let mut matched = by_pattern(storage, value, any);
155 matched.extend(by_pattern(storage, any, value));
156 matched
157 }
158 [index, value] => storage
159 .get(index)
160 .filter(|link| value == any || link.source == value || link.target == value)
161 .copied()
162 .into_iter()
163 .collect(),
164 [index, source, target] if index == any => by_pattern(storage, source, target),
165 [index, source, target] => storage
166 .get(index)
167 .filter(|link| {
168 (source == any || link.source == source) && (target == any || link.target == target)
169 })
170 .copied()
171 .into_iter()
172 .collect(),
173 _ => Vec::new(),
174 }
175}
176
177impl Links<u32> for LinkStorage {
178 fn constants(&self) -> &LinksConstants<u32> {
179 link_storage_constants()
180 }
181
182 fn count_links(&self, query: &[u32]) -> u32 {
183 matching(self, query).len() as u32
184 }
185
186 fn create_links(
187 &mut self,
188 _query: &[u32],
189 handler: WriteHandler<'_, u32>,
190 ) -> Result<Flow, Error<u32>> {
191 let index = self.create(0, 0);
192 let created = Link::new(index, 0, 0);
193 Ok(handler(DoubletsLink::nothing(), as_doublets_link(&created)))
194 }
195
196 fn each_links(&self, query: &[u32], handler: ReadHandler<'_, u32>) -> Flow {
197 for link in matching(self, query) {
198 if handler(as_doublets_link(&link)) == Flow::Break {
199 return Flow::Break;
200 }
201 }
202 Flow::Continue
203 }
204
205 fn update_links(
206 &mut self,
207 query: &[u32],
208 change: &[u32],
209 handler: WriteHandler<'_, u32>,
210 ) -> Result<Flow, Error<u32>> {
211 let index = part(query, 0);
212 let source = part(change, 1);
213 let target = part(change, 2);
214 let before = self
215 .update_raw(index, source, target)
216 .map_err(|_| Error::NotExists(index))?;
217 let after = Link::new(index, source, target);
218 Ok(handler(as_doublets_link(&before), as_doublets_link(&after)))
219 }
220
221 fn delete_links(
222 &mut self,
223 query: &[u32],
224 handler: WriteHandler<'_, u32>,
225 ) -> Result<Flow, Error<u32>> {
226 let index = part(query, 0);
227 let before = self
228 .delete_raw(index)
229 .map_err(|_| Error::NotExists(index))?;
230 Ok(handler(as_doublets_link(&before), DoubletsLink::nothing()))
231 }
232}
233
234impl Doublets<u32> for LinkStorage {
235 fn get_link(&self, index: u32) -> Option<DoubletsLink<u32>> {
236 self.get(index).map(as_doublets_link)
237 }
238}
239
240/// `doublets` ships no blanket implementation for references, so decorating a
241/// borrowed store (instead of moving it into the decorator) needs this
242/// forwarding impl. It is what lets [`LinkStorage`] decorate itself for the
243/// duration of a single operation.
244impl Links<u32> for &mut LinkStorage {
245 fn constants(&self) -> &LinksConstants<u32> {
246 (**self).constants()
247 }
248
249 fn count_links(&self, query: &[u32]) -> u32 {
250 (**self).count_links(query)
251 }
252
253 fn create_links(
254 &mut self,
255 query: &[u32],
256 handler: WriteHandler<'_, u32>,
257 ) -> Result<Flow, Error<u32>> {
258 (**self).create_links(query, handler)
259 }
260
261 fn each_links(&self, query: &[u32], handler: ReadHandler<'_, u32>) -> Flow {
262 (**self).each_links(query, handler)
263 }
264
265 fn update_links(
266 &mut self,
267 query: &[u32],
268 change: &[u32],
269 handler: WriteHandler<'_, u32>,
270 ) -> Result<Flow, Error<u32>> {
271 (**self).update_links(query, change, handler)
272 }
273
274 fn delete_links(
275 &mut self,
276 query: &[u32],
277 handler: WriteHandler<'_, u32>,
278 ) -> Result<Flow, Error<u32>> {
279 (**self).delete_links(query, handler)
280 }
281}
282
283impl Doublets<u32> for &mut LinkStorage {
284 fn get_link(&self, index: u32) -> Option<DoubletsLink<u32>> {
285 (**self).get_link(index)
286 }
287}