Skip to main content

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}