1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//! Decorators that keep `(source, target)` pairs unique.
//!
//! Ports of C# `LinksUniquenessValidator`, `LinksUniquenessResolver` and
//! `LinksCascadeUniquenessAndUsagesResolver`.
use std::{
fmt::{self, Debug, Formatter},
marker::PhantomData,
};
use data::{Flow, LinkReference, LinksConstants};
use super::{
helpers,
macros::{decorator_struct, forward},
};
use crate::{
data::{ReadHandler, WriteHandler},
Doublets, Error, Fuse, Link, Links,
};
decorator_struct! {
/// Rejects an update that would make two links share the same `(source, target)` pair.
///
/// Port of C# `LinksUniquenessValidator`. The update fails with
/// [`Error::AlreadyExists`], the analogue of C#'s
/// `LinkWithSameValueAlreadyExistsException`.
UniquenessValidator
}
impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessValidator<T, L> {
forward!(count_links, each_links, create_links, delete_links);
#[inline]
fn update_links(
&mut self,
query: &[T],
change: &[T],
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>> {
let constants = self.links.constants();
let source = helpers::part(change, constants.source_part);
let target = helpers::part(change, constants.target_part);
helpers::ensure_does_not_exist(&self.links, source, target)?;
self.links.update_links(query, change, handler)
}
}
decorator_struct! {
/// Redirects an update that would produce a duplicate to the link that already exists,
/// then deletes the now-redundant link.
///
/// Port of C# `LinksUniquenessResolver`. Composed with the default
/// [`Doublets::create_link`] this turns `create_link` into the equivalent of
/// [`Doublets::get_or_create`]: the temporary link created first is deleted, and the
/// surviving link is reported to the handler.
///
/// # Deviation from C\#
///
/// C# reports nothing for the surviving link, so `CreateAndUpdate` returns the address
/// of the link it just deleted. This port reports the surviving link as
/// `(before, after)` instead, so [`Doublets::update_by`] and [`Doublets::create_link`]
/// return an address that is actually valid.
///
/// # Requested address
///
/// This decorator resolves pair conflicts and nothing else. It does not check that
/// the link being updated exists. Like the C# original, it looks up the new pair
/// first and deletes the old link only if it still exists. When the old address is
/// missing and the pair is already stored, the update therefore reports the existing
/// link and changes nothing. A missing address with a new pair is forwarded, so the
/// inner layers decide. See [`CascadeUniquenessAndUsagesResolver`] for how to select
/// the `Error::NotExists` contract explicitly.
UniquenessResolver
}
impl<T: LinkReference, L: Doublets<T>> UniquenessResolver<T, L> {
/// Port of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
#[inline]
fn resolve_address_change_conflict(
&mut self,
old: T,
new: T,
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>> {
resolve_address_change_conflict(self, old, new, handler)
}
}
impl<T: LinkReference, L: Doublets<T>> Links<T> for UniquenessResolver<T, L> {
forward!(count_links, each_links, create_links, delete_links);
#[inline]
fn update_links(
&mut self,
query: &[T],
change: &[T],
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>> {
let constants = self.links.constants();
let source = helpers::part(change, constants.source_part);
let target = helpers::part(change, constants.target_part);
let old = helpers::part(query, constants.index_part);
let Some(new) = self.links.search(source, target) else {
return self.links.update_links(query, change, handler);
};
self.resolve_address_change_conflict(old, new, handler)
}
}
decorator_struct! {
/// Like [`UniquenessResolver`], but first re-points every link that referenced the
/// redundant link at the surviving one.
///
/// Port of C# `LinksCascadeUniquenessAndUsagesResolver`.
///
/// # Requested address
///
/// Like [`UniquenessResolver`], this decorator does not check that the link being
/// updated exists. Existence is a separate policy, so callers who guarantee valid
/// addresses pay for no extra lookup. When the old address is missing and the new
/// pair is already stored, the update reports the existing link and changes
/// nothing; when the pair is new, the update is forwarded to the inner layers.
///
/// To get [`Error::NotExists`] for a missing address, select that contract
/// explicitly. Compose [`InnerReferenceExistenceValidator`] *outside* the resolver,
/// so it runs before the pair lookup. It also checks the new endpoints. Or call
/// [`Doublets::try_get_link`] before the updates that need it. A validator placed
/// inside the resolver is never reached for an existing pair.
///
/// ```
/// use doublets::{decorators::DecoratorsExt, unit, Doublets, Error};
/// use mem::Global;
/// # fn main() -> Result<(), Error<u32>> {
/// let mut store = unit::Store::<u32, _>::new(Global::new())?
/// .with_automatic_uniqueness_and_usages_resolution()
/// .with_inner_reference_existence_validation();
/// store.create_point()?;
/// store.create_point()?;
/// let link = store.create_link(1, 2)?;
/// assert!(matches!(store.update(99, 3, 3), Err(Error::NotExists(99))));
/// assert!(matches!(store.update(99, 1, 2), Err(Error::NotExists(99))));
/// assert_eq!(store.search(1, 2), Some(link));
///
/// // Without the validator only the pair is resolved: link 99 is not checked.
/// let mut store = unit::Store::<u32, _>::new(Global::new())?
/// .with_automatic_uniqueness_and_usages_resolution();
/// store.create_point()?;
/// store.create_point()?;
/// store.create_link(1, 2)?;
/// // Per-call alternative: look the address up only where you need the error.
/// assert!(matches!(store.try_get_link(99), Err(Error::NotExists(99))));
/// assert_eq!(store.update(99, 1, 2)?, link);
/// assert_eq!(store.count(), 3);
/// # Ok(())
/// # }
/// ```
///
/// [`InnerReferenceExistenceValidator`]: super::InnerReferenceExistenceValidator
CascadeUniquenessAndUsagesResolver
}
impl<T: LinkReference, L: Doublets<T>> CascadeUniquenessAndUsagesResolver<T, L> {
/// Port of C# `LinksCascadeUniquenessAndUsagesResolver.ResolveAddressChangeConflict`.
#[inline]
fn resolve_address_change_conflict(
&mut self,
old: T,
new: T,
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>> {
let mut fuse = Fuse::new(&mut *handler);
// C# uses the facade here so the merge recurses through the whole stack; the
// static equivalent is `self`, which is this layer plus everything it wraps.
helpers::merge_usages(self, old, new, |before, after| fuse.call(before, after))?;
resolve_address_change_conflict(self, old, new, &mut |before, after| {
fuse.call(before, after)
})
}
}
impl<T: LinkReference, L: Doublets<T>> Links<T> for CascadeUniquenessAndUsagesResolver<T, L> {
forward!(count_links, each_links, create_links, delete_links);
#[inline]
fn update_links(
&mut self,
query: &[T],
change: &[T],
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>> {
let constants = self.links.constants();
let source = helpers::part(change, constants.source_part);
let target = helpers::part(change, constants.target_part);
let old = helpers::part(query, constants.index_part);
let Some(new) = self.links.search(source, target) else {
return self.links.update_links(query, change, handler);
};
self.resolve_address_change_conflict(old, new, handler)
}
}
/// Shared body of C# `LinksUniquenessResolver.ResolveAddressChangeConflict`.
///
/// `links` is the decorator itself, standing in for C#'s `_facade`, so the cascading
/// delete runs through every layer of the stack.
#[inline]
fn resolve_address_change_conflict<T, D>(
links: &mut D,
old: T,
new: T,
handler: WriteHandler<'_, T>,
) -> Result<Flow, Error<T>>
where
T: LinkReference,
D: Doublets<T>,
{
let mut fuse = Fuse::new(&mut *handler);
if old != new && links.exist(old) {
links.delete_links(&[old], &mut |before, after| fuse.call(before, after))?;
}
let survivor = links.try_get_link(new)?;
Ok(fuse.call(survivor.clone(), survivor))
}