dson/
dotstores.rs

1// (c) Copyright 2025 Helsing GmbH. All rights reserved.
2//! # Dot Stores
3//!
4//! This module defines the core data structures, known as "dot stores", that underpin the DSON
5//! (JSON CRDT Using Delta-Mutations For Document Stores) implementation. The concepts and data
6//! structures defined here are based on the research paper "[DSON: JSON CRDT Using
7//! Delta-Mutations For Document Stores](dson_paper.txt)".
8//!
9//! ## Overview
10//!
11//! At the heart of DSON is the idea of a **dot store**, a container for data-type-specific
12//! information that stores the state of a delta-based CRDT. Each dot store is paired with a
13//! [`CausalContext`], which tracks the set of observed events (dots) across replicas. This
14//! combination, encapsulated in the [`CausalDotStore`] struct, forms the basis for building
15//! CRDTs.
16//!
17//! The primary dot stores defined in this module are:
18//!
19//! - [`DotFun`]: A map from [`Dot`]s to values, where the set of dots is its keyset. This is used
20//!   to implement simple CRDTs like [`MvReg`](crate::crdts::mvreg::MvReg) (multi-value registers).
21//! - [`DotMap`]: A map from an arbitrary key type to a `DotStore`, where the computed dots are the
22//!   union of the dots of its values. This is used to implement OR-Maps (Observed-Remove Maps).
23//! - [`DotFunMap`]: A map from [`Dot`]s to `DotStore`s, combining the properties of `DotFun` and
24//!   `DotMap`. This is used to implement OR-Arrays (Observed-Remove Arrays).
25//!
26//! These dot stores are designed to be composable, allowing for the construction of arbitrarily
27//! nested JSON-like structures.
28//!
29//! ## Join Operations
30//!
31//! The core of the CRDT logic is the `join` operation, defined in the [`DotStoreJoin`] trait. The
32//! `join` operation merges the state of two `CausalDotStore`s, resolving conflicts in a
33//! deterministic way. The exact semantics of the join operation vary depending on the concrete
34//! dot store type, but the general principle is to keep the most up-to-date values and discard
35//! those that have been causally overwritten.
36//!
37//! ## References
38//!
39//! The theoretical foundations for the dot stores and their join operations are detailed in
40//! the DSON paper. In particular, see the following sections:
41//!
42//! - **Section 3.3**: Introduces the concept of dot stores and defines `DotFun` and `DotMap`.
43//! - **Section 4**: Describes the observed-remove semantics used in DSON.
44//! - **Section 5**: Introduces the `CompDotFun` (here named `DotFunMap`) and the OR-Array
45//!   algorithm.
46//!
47//! The original work on delta-based CRDTs can be found in the 2018 paper _Delta state replicated
48//! data types_ by Paulo Sérgio Almeida, Ali Shoker, and Carlos Baquero.
49
50use crate::{
51    CausalContext, Dot, DsonRandomState, create_map, create_map_with_capacity,
52    sentinel::{DummySentinel, KeySentinel, Sentinel, ValueSentinel, Visit},
53};
54use smallvec::SmallVec;
55use std::{borrow::Borrow, collections::HashMap, fmt, hash::Hash, ops::Index};
56
57/// A [`DotStore`] paired with a [`CausalContext`].
58///
59/// This is the fundamental building block of the DSON CRDT. It combines a `DotStore`, which holds
60/// the state of a specific data type, with a `CausalContext`, which tracks the set of observed
61/// events (dots) across replicas. This pairing allows for the implementation of delta-based
62/// CRDTs, where changes can be calculated and transmitted as deltas rather than entire states.
63#[derive(Debug, Clone, PartialEq)]
64#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
65pub struct CausalDotStore<DS> {
66    /// The data-type-specific information.
67    pub store: DS,
68    /// The causal context, tracking observed events.
69    pub context: CausalContext,
70}
71
72impl<'cs, DS> From<&'cs CausalDotStore<DS>> for (&'cs DS, &'cs CausalContext) {
73    fn from(cds: &'cs CausalDotStore<DS>) -> Self {
74        (&cds.store, &cds.context)
75    }
76}
77
78impl<'cs, DS> From<&'cs mut CausalDotStore<DS>> for (&'cs DS, &'cs mut CausalContext) {
79    fn from(cds: &'cs mut CausalDotStore<DS>) -> Self {
80        (&cds.store, &mut cds.context)
81    }
82}
83
84impl<DS> CausalDotStore<DS>
85where
86    DS: DotStore,
87{
88    /// Returns true if this is ⊥ (that is, empty).
89    ///
90    /// NOTE: the DSON paper does not explicitly define what a bottom is for a Causal⟨DotStore⟩, but
91    /// it does provide that "For any 𝑋 ∈ Causal⟨DotStore⟩, 𝑋 ⊔ ⊥ = 𝑋", which constrains it to ⊥ =
92    /// ({}, {}), since that is the only value that satisfies that equation and Equation 4 for any
93    /// arbitrary 𝑋.
94    pub fn is_bottom(&self) -> bool {
95        self.store.is_bottom() && self.context.is_empty()
96    }
97
98    /// Returns a subset-CRDT derived from `self` that allows inflating state at the vector time
99    /// `frontier` to what is in `self`.
100    ///
101    /// Does not include deletions not represented by `self.context - frontier` (that is, deletions of
102    /// already-known store entries); that is left as an exercise for the caller.
103    pub fn subset_for_inflation_from(&self, frontier: &CausalContext) -> CausalDotStore<DS>
104    where
105        DS: Clone + Default,
106    {
107        // our goal here is to produce a CRDT that contains aspects of `self.store` that are _not_
108        // known to `frontier`. this could be additions (the easy case), but could also be deletes.
109        // since deletes are handled via the _absence_ of state plus the _presence_ of a dot, we
110        // need to carefully construct both the (delta) store and (delta) context here.
111        //
112        // the state isn't too bad: we call subset_for_inflation_from recursively all the way
113        // "down" the CRDT, keeping only entries whose dot is not in frontier, or that are on the
114        // path _to_ such a dot.
115        //
116        // the context is trickier: we can't send all of self.context since we're excluding values
117        // that aren't in context. more concretely, consider what happens if (0, 1) => 'a', and the
118        // dot (0, 1) is in both self.context and frontier. we will _not_ include it in the delta
119        // of store (since the node represented by frontier already knows it). if we nevertheless
120        // include (0, 1) in the delta context, the semantics of that is a _deletion_ of (0, 1)
121        // [see DotFun::join], which obviously isn't correct. instead, we produce the delta context
122        //
123        //     self.context - frontier + delta_store.dots()
124        //
125        // this indicates that we a) have included anything we know that frontier does not, and b)
126        // acknowledges that some dots are included simply by virtue of being on the path to
127        // new/updated values.
128        let delta_store = self.store.subset_for_inflation_from(frontier);
129        let mut delta_context = &self.context - frontier;
130
131        // NOTE: it _could_ be that nothing bad happens if we don't add in delta_store.dots(),
132        // but that relies on the join at the other end not getting confused by the presence of a
133        // dot in store but not in context. Feels safer to just join them here anyway.
134        delta_store.add_dots_to(&mut delta_context);
135
136        // unfortunately, this doesn't capture deletions. remember from above, the way a delete is
137        // represented is the _presence_ of the dot that inserted a value `context`, coupled with
138        // the _absence_ of its value in `store`.
139        //
140        // consider what happens if A and B are fully synchronized, and both hold, say, just an
141        // MVReg with (1, 1) => 'x' as well as the causal context {(1,1)}. now, A deletes 'x'. this
142        // does not generate a dot, so A's causal context is the same. when A and B next
143        // synchronize, A does not know solely from B's causal context ({(1,1)}) that it is missing
144        // the deletion of (1, 1) => 'x'. the store won't include (1,1) [and shouldn't],
145        // self.context - frontier is empty, and so is delta_store.dots() [since delta_store is
146        // bottom].
147        //
148        // even if we _do_ associate a dot with a deletion (e.g., writing the ALIVE field like we
149        // do for maps and arrays), it doesn't solve this problem. A would then generate (1,2) for
150        // the deletion, and would realize B doesn't have (1,2), *but* it won't know that that
151        // implies that the causal context of the delta it sends to be should therefore include
152        // (1,1). it doesn't know the relationship between (1,2) and (1,1).
153        //
154        // we could keep a "graveyard" that holds
155        //
156        //   Dot(A, B) ---(deleted)----> Dot(X, Y)
157        //
158        // and then here _also_ add in Dot(X, Y) for any Dot(A, B) not in frontier, but that raises
159        // the question of how to garbage-collect said graveyard. it's also not clear what happens
160        // for types where a deletion actually implies _multiple_ removed dots.
161        //
162        // for now, we leave making the context reflect deletions as an exercise for the caller.
163
164        CausalDotStore {
165            store: delta_store,
166            context: delta_context,
167        }
168    }
169}
170
171impl<DS> Default for CausalDotStore<DS>
172where
173    DS: Default,
174{
175    fn default() -> Self {
176        Self {
177            store: Default::default(),
178            context: Default::default(),
179        }
180    }
181}
182
183impl<DS> CausalDotStore<DS> {
184    /// Constructs a new empty [`CausalDotStore`].
185    pub fn new() -> Self
186    where
187        DS: Default,
188    {
189        Self::default()
190    }
191}
192
193#[cfg(any(test, feature = "arbitrary"))]
194use crate::dotstores::recording_sentinel::RecordingSentinel;
195
196impl<DS> CausalDotStore<DS> {
197    /// Joins the given [`CausalDotStore`] with this one, and returns the join.
198    ///
199    /// This is a convenience function around [`CausalDotStore::join_with`].
200    pub fn join<S>(mut self, other: Self, sentinel: &mut S) -> Result<CausalDotStore<DS>, S::Error>
201    where
202        S: Sentinel,
203        DS: DotStoreJoin<S> + Default,
204    {
205        self.consume(other, sentinel)?;
206        Ok(CausalDotStore {
207            store: self.store,
208            context: self.context,
209        })
210    }
211
212    // variant of join intended for tests, so it is not built for performance (for example, it clones eagerly
213    // internally to make the interface more convenient) and it exposes internal bits (like
214    // `on_dot_change`)
215    #[cfg(any(test, feature = "arbitrary"))]
216    pub fn test_join<S>(
217        &self,
218        other: &Self,
219        on_dot_change: &mut dyn FnMut(DotChange),
220        sentinel: &mut S,
221    ) -> Result<CausalDotStore<DS>, S::Error>
222    where
223        S: Sentinel,
224        DS: DotStoreJoin<S> + DotStoreJoin<RecordingSentinel> + Default + Clone,
225    {
226        let mut this = self.clone();
227        this.test_join_with_and_track(
228            other.store.clone(),
229            &other.context,
230            on_dot_change,
231            sentinel,
232        )?;
233
234        Ok(CausalDotStore {
235            store: this.store,
236            context: this.context,
237        })
238    }
239
240    #[cfg(any(test, feature = "arbitrary"))]
241    pub fn test_join_with_and_track<S>(
242        &mut self,
243        store: DS,
244        context: &CausalContext,
245        on_dot_change: &mut dyn FnMut(DotChange),
246        sentinel: &mut S,
247    ) -> Result<(), S::Error>
248    where
249        DS: DotStoreJoin<S> + DotStoreJoin<RecordingSentinel> + Clone + Default,
250        S: Sentinel,
251    {
252        #[cfg(debug_assertions)]
253        {
254            // We do a dry_join here first, to ensure that dry-join
255            // and join always result in the same set of calls being
256            // made to Sentinel. This is an invariant that we want to always
257            // hold, so we check it in debug builds for all test cases using this function.
258
259            let mut dry_join_sentinel = RecordingSentinel::new();
260            let dry_result = <DS as DotStoreJoin<RecordingSentinel>>::dry_join(
261                (&self.store, &self.context),
262                (&store, context),
263                &mut dry_join_sentinel,
264            )
265            .expect("RecordingSentinel is infallible");
266
267            let mut full_run_sentinel = RecordingSentinel::new();
268            let full_result = DS::join(
269                (self.store.clone(), &self.context),
270                (store.clone(), context),
271                &mut |_| {},
272                &mut full_run_sentinel,
273            )
274            .expect("RecordingSentinel is infallible");
275
276            assert_eq!(
277                dry_join_sentinel.changes_seen,
278                full_run_sentinel.changes_seen
279            );
280            assert_eq!(dry_result.is_bottom(), full_result.is_bottom());
281        }
282
283        self.join_with_and_track(store, context, on_dot_change, sentinel)?;
284        Ok(())
285    }
286
287    /// Joins the given [`CausalDotStore`] into this one.
288    ///
289    /// This is a convenience function around [`CausalDotStore::join_with`].
290    pub fn consume<S>(
291        &mut self,
292        other: CausalDotStore<DS>,
293        sentinel: &mut S,
294    ) -> Result<(), S::Error>
295    where
296        DS: DotStoreJoin<S> + Default,
297        S: Sentinel,
298    {
299        self.join_with(other.store, &other.context, sentinel)
300    }
301
302    /// Joins the given [`CausalDotStore`] into this one.
303    ///
304    /// This is a convenience function around [`CausalDotStore::join_with`].
305    #[cfg(any(test, feature = "arbitrary"))]
306    pub fn test_consume(&mut self, other: CausalDotStore<DS>)
307    where
308        DS: DotStoreJoin<RecordingSentinel> + Clone + Default,
309    {
310        self.test_join_with(other.store, &other.context)
311    }
312
313    /// Joins or replaces the current [`CausalDotStore`] with the provided one.
314    ///
315    /// If the current value is bottom, it is replaced wholesale, bypassing the
316    /// join. This method does not accept a sentinel as changes cannot always
317    /// be tracked.
318    pub fn join_or_replace_with(&mut self, store: DS, context: &CausalContext)
319    where
320        DS: DotStoreJoin<DummySentinel> + Default,
321    {
322        if self.is_bottom() {
323            *self = CausalDotStore {
324                store,
325                context: context.clone(),
326            };
327        } else {
328            self.join_with(store, context, &mut DummySentinel)
329                .expect("DummySentinel is Infallible");
330        }
331    }
332
333    /// Joins the given [`DotStore`]-[`CausalContext`] pair into those in `self`.
334    ///
335    /// Prefer this method when you need to avoid cloning the [`CausalContext`].
336    pub fn join_with<S>(
337        &mut self,
338        store: DS,
339        context: &CausalContext,
340        sentinel: &mut S,
341    ) -> Result<(), S::Error>
342    where
343        DS: DotStoreJoin<S> + Default,
344        S: Sentinel,
345    {
346        self.join_with_and_track(store, context, &mut |_| (), sentinel)
347    }
348
349    /// Joins the given [`DotStore`]-[`CausalContext`] pair into those in `self`.
350    ///
351    /// Prefer this method when you need to avoid cloning the [`CausalContext`].
352    #[cfg(any(test, feature = "arbitrary"))]
353    pub fn test_join_with(&mut self, store: DS, context: &CausalContext)
354    where
355        DS: DotStoreJoin<RecordingSentinel> + Clone + Default,
356    {
357        self.test_join_with_and_track(store, context, &mut |_| (), &mut RecordingSentinel::new())
358            .expect("RecordingSentinel is infallible");
359    }
360
361    fn join_with_and_track<S>(
362        &mut self,
363        store: DS,
364        context: &CausalContext,
365        on_dot_change: &mut dyn FnMut(DotChange),
366        sentinel: &mut S,
367    ) -> Result<(), S::Error>
368    where
369        DS: DotStoreJoin<S> + Default,
370        S: Sentinel,
371    {
372        let old_store = std::mem::take(&mut self.store);
373        self.store = DS::join(
374            (old_store, &self.context),
375            (store, context),
376            on_dot_change,
377            sentinel,
378        )?;
379        self.context.union(context);
380        Ok(())
381    }
382}
383
384impl<DS> CausalDotStore<DS> {
385    /// Constructs a new [`CausalDotStore`] by applying the given function to the current store.
386    ///
387    /// This method keeps the causal context as-is.
388    pub fn map_store<DS2>(self, m: impl FnOnce(DS) -> DS2) -> CausalDotStore<DS2> {
389        CausalDotStore {
390            store: (m)(self.store),
391            context: self.context,
392        }
393    }
394
395    /// Constructs a new [`CausalDotStore`] by applying the given function to the current context.
396    ///
397    /// This method keeps the store as-is.
398    pub fn map_context(self, m: impl FnOnce(CausalContext) -> CausalContext) -> CausalDotStore<DS> {
399        CausalDotStore {
400            store: self.store,
401            context: (m)(self.context),
402        }
403    }
404
405    /// Calls a function with a reference to the contained store.
406    ///
407    /// Returns the original [`CausalDotStore`] unchanged.
408    pub fn inspect(self, f: impl FnOnce(&DS)) -> CausalDotStore<DS> {
409        f(&self.store);
410        self
411    }
412}
413
414/// A container for data-type specific information that stores the state of a 𝛿-based CRDT.
415///
416/// This trait defines the common interface for all dot stores. It provides methods for querying
417/// the dots contained within the store, checking if the store is empty (i.e., ⊥), and creating a
418/// subset of the store for inflation.
419pub trait DotStore {
420    /// Queries the set of event identifiers (ie, dots) currently stored in the dot store.
421    ///
422    /// Has a default implementation that creates an empty [`CausalContext`] and invokes
423    /// `add_dots_to`.
424    fn dots(&self) -> CausalContext {
425        let mut cc = CausalContext::default();
426        self.add_dots_to(&mut cc);
427        cc
428    }
429
430    /// Add the set of event identifiers (ie, dots) currently stored in the dot store to `other`.
431    ///
432    /// Should not compact the resulting `CausalContext`.
433    fn add_dots_to(&self, other: &mut CausalContext);
434
435    /// Returns true if this dot store is ⊥ (ie, empty).
436    fn is_bottom(&self) -> bool;
437
438    /// Returns a subset-CRDT derived from `self` that allows inflating state at the vector time
439    /// `frontier` to what's in `self`.
440    fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self;
441}
442
443/// An observed change to a dot store.
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
445pub enum DotChange {
446    /// The given dot was added to the store.
447    Add(Dot),
448    /// The given dot was removed from the store.
449    Remove(Dot),
450}
451
452/// The outcome of performing a dry-join.
453///
454/// When doing a dry-join, we don't perform the join completely.
455/// However, we often need to know whether the join would have
456/// been bottom or not, had it been carried out. This single
457/// bit of information is significantly cheaper to calculate than
458/// a full join.
459#[derive(Debug)]
460pub struct DryJoinOutput {
461    /// True if the output of the dry-join was ⊥ (bottom).
462    is_bottom: bool,
463}
464
465impl DryJoinOutput {
466    /// Create a join-result representing ⊥
467    pub fn bottom() -> Self {
468        Self { is_bottom: true }
469    }
470    /// Update self, setting it to 'not bottom'
471    pub fn set_is_not_bottom(&mut self) {
472        self.is_bottom = false;
473    }
474    pub fn new(is_bottom: bool) -> Self {
475        Self { is_bottom }
476    }
477    /// If parameter is false, set self.is_bottom to false.
478    /// Intuitively this is a join of two dry-join results.
479    pub fn union_with(&mut self, other: DryJoinOutput) {
480        if !other.is_bottom {
481            self.is_bottom = false;
482        }
483    }
484    /// Like `join_with`, but returns the result instead.
485    pub fn union(&self, other: Self) -> Self {
486        Self {
487            is_bottom: self.is_bottom && other.is_bottom,
488        }
489    }
490    /// Returns true if this instance represents ⊥ (bottom)
491    pub fn is_bottom(&self) -> bool {
492        self.is_bottom
493    }
494}
495
496/// A trait for dot stores that can be joined.
497///
498/// This trait defines the `join` and `dry_join` operations, which are the core of the CRDT
499/// logic. The `join` operation merges the state of two dot stores, while `dry_join` simulates a
500/// join without actually modifying the state, which is useful for validation.
501pub trait DotStoreJoin<S>: DotStore {
502    /// Computes the join (⊔) between two CausalDotStores.
503    ///
504    /// Note that for efficiency this does not take a [`CausalDotStore`] directly, but instead
505    /// takes owned [`DotStore`]s and a shared reference to the [`CausalContext`] to avoid
506    /// excessive cloning.
507    ///
508    /// Quoth the DSON paper:
509    ///
510    /// > For any 𝑋 ∈ Causal⟨DotStore⟩, 𝑋 ⊔ ⊥ = 𝑋.
511    /// >
512    /// > For two elements 𝑋1, 𝑋2 ∈ Causal⟨DotStore⟩
513    /// > we say that 𝑋1 < 𝑋2 iff ∃𝑋 ≠ ⊥ ∈ Causal⟨DotStore⟩ such that 𝑋1 ⊔ 𝑋 = 𝑋2.
514    ///
515    /// > An example of a ⊥ value is a merge between two elements of the Causal⟨DotFun⟩
516    /// > semilattice, where the domains are disjoint but all mappings are in the others causal
517    /// > history. Consider for example a write 𝑤1 that precedes a write 𝑤2, i.e., 𝑤1 ≺𝜎 𝑤2, then
518    /// > the dot generated by 𝑤1 is in the causal context of the delta generated by 𝑤2. By the
519    /// > definition of join, the mapping doesn’t “survive” the join, and therefore the old value
520    /// > (written by 𝑤1) is overwritten – it isn’t present in the range of the map after 𝑤2.
521    ///
522    /// The exact semantics of a DotStore's join varies depending on the concrete type used.
523    ///
524    /// # Observing changes
525    /// Join (⊔) operations are commutative, i.e. 𝑋1 ⊔ 𝑋2 = 𝑋2 ⊔ 𝑋1, so the order of arguments
526    /// ds1 and ds2 doesn't matter w.r.t. the final result. However, conventionally we interpret
527    /// ds1 as the current state and ds2 as an incoming delta, so from the perspective of the
528    /// sentinel, changes are applied from ds2 into ds1. The same applies to `on_dot_change`.
529    fn join(
530        ds1: (Self, &CausalContext),
531        ds2: (Self, &CausalContext),
532        on_dot_change: &mut dyn FnMut(DotChange),
533        sentinel: &mut S,
534    ) -> Result<Self, S::Error>
535    where
536        Self: Sized,
537        S: Sentinel;
538
539    // YADR: 2024-10-10 Implementation of DryJoin
540    //
541    // In the context of ensuring the schema-conformance of deltas, we faced the challenge of
542    // how to efficiently validate such deltas prior to merging them into the canonical root
543    // document.
544    //
545    // We decided for adding a "dry" join that duplicates the join logic but doesn't change any
546    // document state as it runs, and neglected alternatives that would require cloning the root
547    // document, keeping an undo log, or allow a sentinel to exit after applying a subset of the
548    // changes (further details at the end of the YADR).
549    //
550    // We did this to achieve minimal performance overhead (cloning), complexity (undo-log), and
551    // surprise factor (partial application) of performing delta validations, accepting the
552    // duplication of code between join and dry-join as well as the maintenance burden this
553    // brings when adding or updating CRDTs.
554    //
555    // We think this is the right trade-off because adding new CRDTs and changing old ones is
556    // uncommon, performance of validation is paramount given its frequency, and debugging
557    // two nearly-identical implementations is likely to be easier than debugging a
558    // join + undo-log combination.
559    // Alternatives that were considered and rejected:
560    //
561    // * Do the regular join, but keep an undo-log. If a validation error is detected,
562    //   the delta is undone. This was deemed relatively hard to implement.
563    // * Clone the entire document before the join, then do a regular join. This
564    //   does not perform well. Experiments show a cost of ~50ms on fast machines, for reasonably
565    //   sized documents (in the 10s of megabytes).
566    // * Do a regular join, but allow the sentinel to veto changes. This was deemed difficult
567    //   to implement, and to define the semantics of. It would also result in partial updated,
568    //   which is generally undesirable and hard to reason about.
569    // * Implement DryJoin and regular join using the same code. Create abstractions and
570    //   use generics as needed to achieve this. A simplified, but functional, prototype using this
571    //   approach was written. The complexity was deemed undesirable.
572
573    /// Simulates a [`DotStoreJoin::join`] without constructing the output of the join.
574    ///
575    /// This simulation allows a sentinel to observe a join without committing its result,
576    /// such as to validate a delta prior to joining it.
577    ///
578    /// Since this method does not have to construct the join output, it does not need to take
579    /// ownership of its parameters (ie, it can be run on shared references to the dot stores).
580    ///
581    /// This method returns an indicator determining if the result of the real join would have
582    /// been the bottom type.
583    fn dry_join(
584        ds1: (&Self, &CausalContext),
585        ds2: (&Self, &CausalContext),
586        sentinel: &mut S,
587    ) -> Result<DryJoinOutput, S::Error>
588    where
589        Self: Sized,
590        S: Sentinel;
591}
592
593/// A map from [`Dot`] to `V` whose computed dots is its keyset.
594///
595/// Quoth the DSON paper:
596///
597/// > A join of Causal⟨DotFun⟩ keeps values that exist in both of the mappings and merges their
598/// > respective values, or that exist in either one of the mappings and are “new” to the other in
599/// > the sense that they are not in its causal history.
600///
601/// In practice, this means that a join of two [`DotFun`] will keep only up-to-date elements. In
602/// particular, if instance X1 has observed some [`Dot`] that exists in X2, but that [`Dot`] is not
603/// present in X1, then that [`Dot`] is _not_ preserved (as it has presumably been removed).
604#[derive(Clone, PartialEq, Eq)]
605#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
606pub struct DotFun<V> {
607    // NOTE: the store is explicitly ordered by dot so that self-healing conflicts arising due
608    // to out-of-order delivery of messages can be dealt with by final consumers by just taking
609    // the last value among the conflicts, thus avoiding the need to access the dots directly. This
610    // implicit resolution strategy works as long as the entry is only ever mutated by a single
611    // `Identifier`, as in that case it is guaranteed that later/higher dots will override their
612    // predecessors once all dots have eventually been observed.
613    state: SmallVec<[(Dot, V); 1]>,
614}
615
616impl<V: fmt::Debug> fmt::Debug for DotFun<V> {
617    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618        f.debug_map().entries(self.iter()).finish()
619    }
620}
621
622// manual impl because auto-derive'd `Clone` requires `V: Clone`.
623impl<V> Default for DotFun<V> {
624    fn default() -> Self {
625        Self {
626            state: Default::default(),
627        }
628    }
629}
630
631/// An iterator over the values of a [`DotFun`].
632pub struct DotFunValueIter<'df, V> {
633    it: std::slice::Iter<'df, (Dot, V)>,
634}
635
636impl<'df, V> Iterator for DotFunValueIter<'df, V> {
637    type Item = &'df V;
638
639    fn next(&mut self) -> Option<Self::Item> {
640        self.it.next().map(|(_, v)| v)
641    }
642
643    fn size_hint(&self) -> (usize, Option<usize>) {
644        self.it.size_hint()
645    }
646
647    fn count(self) -> usize
648    where
649        Self: Sized,
650    {
651        self.it.count()
652    }
653
654    fn last(self) -> Option<Self::Item>
655    where
656        Self: Sized,
657    {
658        self.it.last().map(|(_, v)| v)
659    }
660
661    fn nth(&mut self, n: usize) -> Option<Self::Item> {
662        self.it.nth(n).map(|(_, v)| v)
663    }
664}
665impl<V> ExactSizeIterator for DotFunValueIter<'_, V> {}
666impl<V> Clone for DotFunValueIter<'_, V> {
667    fn clone(&self) -> Self {
668        Self {
669            it: self.it.clone(),
670        }
671    }
672}
673impl<V> fmt::Debug for DotFunValueIter<'_, V>
674where
675    V: fmt::Debug,
676{
677    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678        self.it.fmt(f)
679    }
680}
681
682impl<V> DotFun<V> {
683    /// Constructs a [`DotFun`] with the given initial capacity.
684    pub fn with_capacity(capacity: usize) -> Self {
685        Self {
686            state: SmallVec::with_capacity(capacity),
687        }
688    }
689
690    /// Produces an iterator over the map's keys and values.
691    pub fn iter(&self) -> impl ExactSizeIterator<Item = (Dot, &V)> {
692        self.state.iter().map(|(k, v)| (*k, v))
693    }
694
695    /// Produces an iterator over the map's keys.
696    pub fn keys(&self) -> impl ExactSizeIterator<Item = Dot> + '_ {
697        self.iter().map(|(k, _)| k)
698    }
699
700    /// Produces an iterator over the map's values.
701    pub fn values(&self) -> DotFunValueIter<'_, V> {
702        DotFunValueIter {
703            it: self.state.iter(),
704        }
705    }
706
707    /// Returns the number of keys in the map.
708    pub fn len(&self) -> usize {
709        self.state.len()
710    }
711
712    /// Returns true if the map is empty.
713    pub fn is_empty(&self) -> bool {
714        self.state.is_empty()
715    }
716
717    fn get_index(&self, dot: &Dot) -> Option<usize> {
718        self.state
719            .as_slice()
720            .binary_search_by_key(dot, |(k, _)| *k)
721            .ok()
722    }
723
724    /// Retrieves the associated value, if any, for the given [`Dot`].
725    pub fn get(&self, dot: &Dot) -> Option<&V> {
726        self.get_index(dot).map(|idx| &self.state[idx].1)
727    }
728
729    /// Retrieves a mutable reference to the associated value, if any, for the given [`Dot`].
730    pub fn get_mut(&mut self, dot: &Dot) -> Option<&mut V> {
731        self.get_index(dot).map(|idx| &mut self.state[idx].1)
732    }
733
734    /// Returns `true` if the given [`Dot`] has a value in this map.
735    pub fn has(&self, dot: &Dot) -> bool {
736        self.get_index(dot).is_some()
737    }
738
739    /// Associates the value with the given [`Dot`].
740    ///
741    /// Returns the previous value if any.
742    pub fn set(&mut self, dot: Dot, value: V) -> Option<V> {
743        if let Some(v) = self.get_mut(&dot) {
744            Some(std::mem::replace(v, value))
745        } else {
746            let idx = self.state.partition_point(|(d, _)| *d < dot);
747            self.state.insert(idx, (dot, value));
748            None
749        }
750    }
751
752    /// Removes and returns the value associated with a [`Dot`], if the dot exists.
753    pub fn remove(&mut self, dot: &Dot) -> Option<V> {
754        if let Some(idx) = self.get_index(dot) {
755            // as tempting as it may be, we shouldn't use swap_remove here as we
756            // want to keep the list sorted
757            Some(self.state.remove(idx).1)
758        } else {
759            None
760        }
761    }
762
763    /// Retains only the values for which a predicate is true.
764    pub fn retain(&mut self, mut f: impl FnMut(&Dot, &mut V) -> bool) {
765        self.state.retain(|(k, v)| f(k, v))
766    }
767}
768
769impl<V> DotStore for DotFun<V>
770where
771    V: PartialEq + fmt::Debug + Clone,
772{
773    fn add_dots_to(&self, other: &mut CausalContext) {
774        other.insert_dots(self.keys());
775    }
776
777    fn is_bottom(&self) -> bool {
778        self.is_empty()
779    }
780
781    fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
782        Self {
783            state: self
784                .state
785                .iter()
786                .filter(|(dot, _)| !frontier.dot_in(*dot))
787                .map(|(dot, v)| (*dot, v.clone()))
788                .collect(),
789        }
790    }
791}
792
793impl<V, S> DotStoreJoin<S> for DotFun<V>
794where
795    S: ValueSentinel<V>,
796    V: PartialEq + fmt::Debug + Clone,
797{
798    /// Formally (Equation 4):
799    /// ```text
800    /// > (𝑚, 𝑐) ⊔ (𝑚′, 𝑐′) =
801    /// >   (
802    /// >       {𝑑 ↦ 𝑚[𝑑] ⊔ 𝑚′ [𝑑] | 𝑑 ∈ dom 𝑚 ∩ dom 𝑚′}
803    /// >     ∪ {(𝑑, 𝑣) ∈ 𝑚  | 𝑑 ∉ 𝑐′}
804    /// >     ∪ {(𝑑, 𝑣) ∈ 𝑚′ | 𝑑 ∉ 𝑐}
805    /// >     , 𝑐 ∪ 𝑐′
806    /// >   )
807    /// ```
808    ///
809    /// Informally:
810    ///  - for dots in both stores, join the values
811    ///  - for dots in store 1 that haven't been observed by store 2, keep the value
812    ///  - for dots in store 2 that haven't been observed by store 1, keep the value
813    ///  - don't keep other dots
814    ///  - the resulting causal context is the union of the provided causal contexts
815    fn join(
816        (m1, cc1): (Self, &CausalContext),
817        (mut m2, cc2): (Self, &CausalContext),
818        on_dot_change: &mut dyn FnMut(DotChange),
819        sentinel: &mut S,
820    ) -> Result<Self, S::Error>
821    where
822        S: Sentinel,
823    {
824        // NOTE! When making changes to this method, consider if corresponding
825        // changes need to be done to ::dry_join as well!
826
827        let mut res_m = Self::with_capacity(m1.len().max(m2.len()));
828        for (dot, v1) in m1.state {
829            if let Some(v2) = m2.remove(&dot) {
830                // dots are assumed to be unique, so there's no need to join these as they must by
831                // implication be identical.
832                if v1 != v2 {
833                    // this should be unreachable, since validation should have caught this
834                    unreachable!("duplicate node id detected");
835                }
836
837                res_m.set(dot, v1);
838            } else if !cc2.dot_in(dot) {
839                // m1 has v, m2 does not, but m2 hasn't observed the dot, so we keep v as this can't
840                // be a removal.
841                // TODO(#2): is ds1's author authorized to write this value?
842                res_m.set(dot, v1);
843            } else {
844                // m1 map has a value that m2 does not, _but_ the map that does not have the
845                // value (m2) has already observed the dot in its causal context. So, it must have
846                // intentionally chosen to remove the value, and thus we should not preserve it.
847                // TODO(#2): is ds2's author authorized to clear this value?
848                sentinel.unset(v1)?;
849                on_dot_change(DotChange::Remove(dot));
850            }
851        }
852
853        // m2 has v2, m1 does not, and m1 hasn't observed the dot
854        // meaning v2 should be preserved (it wasn't deleted by m1)
855        // TODO(#2): is ds2's author authorized to write this value?
856        for (dot, v2) in m2.state.into_iter().filter(|(dot, _)| !cc1.dot_in(*dot)) {
857            sentinel.set(&v2)?;
858            res_m.set(dot, v2);
859            on_dot_change(DotChange::Add(dot));
860        }
861
862        Ok(res_m)
863    }
864
865    fn dry_join(
866        (m1, cc1): (&Self, &CausalContext),
867        (m2, cc2): (&Self, &CausalContext),
868        sentinel: &mut S,
869    ) -> Result<DryJoinOutput, S::Error>
870    where
871        Self: Sized,
872        S: Sentinel,
873    {
874        // For explanation of this method, see comments in ::join(..).
875
876        let mut res_m = DryJoinOutput::bottom();
877
878        for (dot, v1) in &m1.state {
879            if let Some(v2) = m2.get(dot) {
880                res_m.set_is_not_bottom();
881                if v1 != v2 {
882                    panic!("duplicate node id detected (in crdt join)")
883                }
884            } else if !cc2.dot_in(*dot) {
885                res_m.set_is_not_bottom();
886            } else {
887                sentinel.unset(v1.clone())?;
888            }
889        }
890
891        for (_dot, v2) in m2
892            .state
893            .iter()
894            .filter(|(dot, _)| !m1.has(dot) && !cc1.dot_in(*dot))
895        {
896            res_m.set_is_not_bottom();
897            sentinel.set(v2)?;
898        }
899
900        Ok(res_m)
901    }
902}
903
904/// A map from [`Dot`] to `V: DotStore`, whose computed dots is the union of the dots of its
905/// values.
906///
907/// Quoth the DSON paper:
908///
909/// > We combine the [`DotMap`] and [`DotFun`] to get a dot store which maps dots to dot stores.
910/// > The join operation keeps keys that have not been deleted (as in the [`DotFun`]), or the
911/// > values themselves haven’t been deleted (as in the [`DotMap`]).
912///
913/// Note that this is called `CompDotFun` in the DSON paper (section 5.1), but `DotFunMap` in their
914/// prototype implementation.
915#[derive(Clone, PartialEq, Eq)]
916#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
917#[doc(alias = "CompDotFun")]
918pub struct DotFunMap<V> {
919    state: HashMap<Dot, V, DsonRandomState>,
920}
921
922impl<V: fmt::Debug> fmt::Debug for DotFunMap<V> {
923    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924        f.debug_map().entries(self.state.iter()).finish()
925    }
926}
927
928impl<V> Default for DotFunMap<V> {
929    fn default() -> Self {
930        Self {
931            state: create_map(),
932        }
933    }
934}
935
936impl<V> DotFunMap<V> {
937    /// Constructs a [`DotFunMap`] with the given initial capacity.
938    pub fn with_capacity(capacity: usize) -> Self {
939        Self {
940            state: create_map_with_capacity(capacity),
941        }
942    }
943
944    /// Produces an iterator over the map's keys and values.
945    pub fn iter(&self) -> impl ExactSizeIterator<Item = (Dot, &V)> {
946        self.state.iter().map(|(&k, v)| (k, v))
947    }
948
949    /// Produces an iterator over the map's keys.
950    pub fn keys(&self) -> impl ExactSizeIterator<Item = Dot> + '_ {
951        self.state.keys().copied()
952    }
953
954    /// Produces an iterator over the map's values.
955    pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
956        self.state.values()
957    }
958
959    /// Returns the number of keys in the map.
960    pub fn len(&self) -> usize {
961        self.state.len()
962    }
963
964    /// Returns true if the map is empty.
965    pub fn is_empty(&self) -> bool {
966        self.state.is_empty()
967    }
968}
969
970impl<V> DotFunMap<V> {
971    /// Retrieves the associated value, if any, for the given [`Dot`].
972    pub fn get(&self, dot: &Dot) -> Option<&V> {
973        self.state.get(dot)
974    }
975
976    /// Retrieves a mutable reference to the associated value, if any, for the given [`Dot`].
977    pub fn get_mut(&mut self, dot: &Dot) -> Option<&mut V> {
978        self.state.get_mut(dot)
979    }
980
981    /// Returns `true` if the given [`Dot`] has a value in this map.
982    pub fn has(&self, dot: &Dot) -> bool {
983        self.state.contains_key(dot)
984    }
985
986    /// Associates the value with the given [`Dot`].
987    ///
988    /// Returns the previous value if any.
989    pub fn set(&mut self, dot: Dot, value: V) -> Option<V> {
990        self.state.insert(dot, value)
991    }
992}
993
994impl<V> FromIterator<(Dot, V)> for DotFunMap<V> {
995    fn from_iter<T: IntoIterator<Item = (Dot, V)>>(iter: T) -> Self {
996        Self {
997            state: HashMap::from_iter(iter),
998        }
999    }
1000}
1001
1002impl<V> DotStore for DotFunMap<V>
1003where
1004    V: DotStore + fmt::Debug,
1005{
1006    fn add_dots_to(&self, other: &mut CausalContext) {
1007        // NOTE: Equation 6 in the paper suggests this should also include self.keys(),
1008        //            but the original implementation does not. the text just before eq6 also says
1009        //            "Note that the dots method returns the dots in the domain, as well as a union
1010        //            on recursive calls of dots on all dot stores in the range", which again,
1011        //            doesn't seem true in the implementation. so we do what we think is right.
1012        //            This was confirmed by one of the DSON paper authors (Arik Rinberg) by email
1013        //            on 2023-08-25.
1014        other.insert_dots(self.keys());
1015        for v in self.values() {
1016            v.add_dots_to(other);
1017        }
1018    }
1019
1020    fn is_bottom(&self) -> bool {
1021        self.state.is_empty()
1022    }
1023
1024    fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
1025        let mut delta = Self {
1026            state: create_map_with_capacity(self.state.len()),
1027        };
1028
1029        for (&dot, v) in &self.state {
1030            let delta_v = v.subset_for_inflation_from(frontier);
1031            if !delta_v.is_bottom() {
1032                // NOTE: we do not consider whether frontier.dot_in(dot), since updates can
1033                // happen _under_ old dots.
1034                delta.state.insert(dot, delta_v);
1035            }
1036        }
1037
1038        delta
1039    }
1040}
1041
1042impl<V, S> DotStoreJoin<S> for DotFunMap<V>
1043where
1044    V: DotStoreJoin<S> + fmt::Debug + Default,
1045    S: Visit<Dot> + KeySentinel,
1046{
1047    /// Formally (Equation 7):
1048    ///
1049    /// > (𝑚, 𝑐) ⊔ (𝑚′, 𝑐′) =
1050    /// >   (
1051    /// >       {𝑑 ↦ 𝑣(𝑑) | 𝑑 ∈ dom 𝑚 ∩ dom 𝑚′ ∧ 𝑣(𝑑) ≠ ⊥}
1052    /// >     ∪ {(𝑑, 𝑣) ∈ 𝑚 | 𝑑 ∉ 𝑐′}
1053    /// >     ∪ {(𝑑, 𝑣) ∈ 𝑚′ | 𝑑 ∉ 𝑐}
1054    /// >     , 𝑐 ∪ 𝑐′
1055    /// >   )
1056    /// >   where 𝑣(𝑑) = fst((𝑚(𝑑), 𝑐) ⊔ (𝑚′(𝑑), 𝑐′))
1057    ///
1058    /// Informally:
1059    ///  - for dots in both stores, join the values and keep non-bottoms
1060    ///  - for dots in store 1 that haven't beeen observed by store 2, keep the value
1061    ///  - for dots in store 2 that haven't beeen observed by store 1, keep the value
1062    ///  - don't keep other dots
1063    ///  - the resulting causal context is the union of the provided causal contexts
1064    fn join(
1065        (m1, cc1): (Self, &CausalContext),
1066        (mut m2, cc2): (Self, &CausalContext),
1067        on_dot_change: &mut dyn FnMut(DotChange),
1068        sentinel: &mut S,
1069    ) -> Result<Self, S::Error>
1070    where
1071        S: Sentinel,
1072    {
1073        // NOTE! When making changes to this method, consider if corresponding
1074        // changes need to be done to ::dry_join as well!
1075
1076        let mut res_m = Self::with_capacity(m1.len().max(m2.len()));
1077        for (dot, v1) in m1.state {
1078            sentinel.enter(&dot)?;
1079            if let Some(v2) = m2.state.remove(&dot) {
1080                let new_v = V::join((v1, cc1), (v2, cc2), on_dot_change, sentinel)?;
1081                if !new_v.is_bottom() {
1082                    res_m.set(dot, new_v);
1083                } else {
1084                    on_dot_change(DotChange::Remove(dot));
1085                }
1086            } else if !cc2.dot_in(dot) {
1087                // m1 has v, m2 does not, but m2 hasn't observed the dot, so we keep v as this can't
1088                // be a removal.
1089                // TODO(#2): is ds1's author authorized to write this value?
1090                // NOTE: the original implementation does not join the value here, and neither
1091                // do we. this may seem odd since presumably cc2 may not have seen the root dot but
1092                // _may_ have seen updates inside of that dot that we need to take into account.
1093                // however, that is actually impossible due to how dots work -- since the dot is
1094                // the key, and cc2 hasn't seen the key dot, it _cannot_ have seen anything inside
1095                // that dot's value either, as that would mean having seen the key, which is the
1096                // dot. it would be _safe_ to do a join here, we just happen to know it _must_ end
1097                // up returning v1 as-is.
1098                res_m.set(dot, v1);
1099            } else {
1100                // m1 map has a value that m2 does not, _but_ the map that does not have the
1101                // value (m2) has already observed the dot in its causal context. So, it must have
1102                // intentionally chosen to remove the value, and thus we should not preserve it.
1103                // TODO(#2): is ds2's author authorized to clear this value?
1104                // NOTE: there's an important subtlety here compared to DotMap -- if the key
1105                // dot is removed, all nested values are also removed _by implication_. that is, we
1106                // do not join v1 with Default and look for bottom (like we do in DotMap for this
1107                // case), but rather take a removal of a root dot as a subtree removal. this is
1108                // reinforced by the DSON paper:
1109                //
1110                // > As the root is stored in a CompDotFun, once a root is removed it never
1111                // > reappears, contrary to keys in a DotMap which may remain undeleted if there is
1112                // > a concurrent update.
1113                //
1114                // this is awkward for sentinels, because it means they are not notified of
1115                // anything that happens inside of such a nested removed-by-implication value (and
1116                // ditto for `on_dot_change`), so we specifically choose to still join here to
1117                // provide that signal. we just join with Default and cc1's own `CausalContext` so
1118                // that we are sure that everything will be treated as a deletion (which would not
1119                // necessarily be the case if we used cc2's `CausalContext`).
1120                on_dot_change(DotChange::Remove(dot));
1121                let new_v = V::join((v1, cc1), (V::default(), cc1), on_dot_change, sentinel)?;
1122                assert!(new_v.is_bottom());
1123                sentinel.delete_key()?;
1124            }
1125            sentinel.exit()?;
1126        }
1127
1128        // m2 has v2, m1 does not, and m1 hasn't observed the dot
1129        // meaning v2 should be preserved (it wasn't deleted by m1)
1130        // TODO(#2): is ds2's author authorized to write this value?
1131        for (dot, v2) in m2.state {
1132            sentinel.enter(&dot)?;
1133            if !cc1.dot_in(dot) {
1134                on_dot_change(DotChange::Add(dot));
1135                // NOTE: as mentioned in the earlier for loop, the DSON paper does not
1136                // indicate a join should be performed here. however, unlike the inverse case
1137                // (`!cc2.dot_in(dot)`) we do the join anyway here so that `sentinel` and
1138                // `on_dot_change` get information about new stuff from v2. this is only important
1139                // because sentinel and on_dot_change aren't symmetrical with respect to v1 and v2
1140                // as per the "Observing changes" section of the `join` docs.
1141                let new_v = V::join((V::default(), cc1), (v2, cc2), on_dot_change, sentinel)?;
1142                sentinel.create_key()?;
1143                res_m.state.insert(dot, new_v);
1144            }
1145            sentinel.exit()?;
1146        }
1147
1148        Ok(res_m)
1149    }
1150    fn dry_join(
1151        (m1, cc1): (&Self, &CausalContext),
1152        (m2, cc2): (&Self, &CausalContext),
1153        sentinel: &mut S,
1154    ) -> Result<DryJoinOutput, S::Error>
1155    where
1156        S: Sentinel,
1157    {
1158        // For explanation of this method, see comments in ::join(..).
1159
1160        let mut res_m = DryJoinOutput::bottom();
1161        for (dot, v1) in m1.state.iter() {
1162            sentinel.enter(dot)?;
1163            if let Some(v2) = m2.state.get(dot) {
1164                let new_v = V::dry_join((v1, cc1), (v2, cc2), sentinel)?;
1165                if !new_v.is_bottom() {
1166                    res_m.set_is_not_bottom();
1167                }
1168            } else if !cc2.dot_in(*dot) {
1169                res_m.set_is_not_bottom();
1170            } else {
1171                let default_v = V::default();
1172                let new_v = V::dry_join((v1, cc1), (&default_v, cc1), sentinel)?;
1173                assert!(new_v.is_bottom());
1174                sentinel.delete_key()?;
1175            }
1176            sentinel.exit()?;
1177        }
1178
1179        for (dot, v2) in m2.state.iter().filter(|(dot, _v2)| !m1.has(dot)) {
1180            sentinel.enter(dot)?;
1181            if !cc1.dot_in(*dot) {
1182                let _new_v = V::dry_join((&V::default(), cc1), (v2, cc2), sentinel)?;
1183                sentinel.create_key()?;
1184                // Just inserting a value to a map makes that map be not-bottom.
1185                res_m.set_is_not_bottom();
1186            }
1187            sentinel.exit()?;
1188        }
1189
1190        Ok(res_m)
1191    }
1192}
1193
1194/// A map from an arbitrary key type to a `V: DotStore`, whose computed dots is the union of the
1195/// dots of its values.
1196///
1197/// This is used to implement OR-Maps (Observed-Remove Maps).
1198///
1199/// Quoth the DSON paper:
1200///
1201/// > The merge in the Causal⟨DotMap⟩ applies the merge recursively on each of the keys in either
1202/// > domains, and keeps all non-⊥ values.
1203#[derive(Clone)]
1204#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
1205pub struct DotMap<K, V> {
1206    #[cfg_attr(
1207        feature = "serde",
1208        serde(bound(
1209            serialize = "K: Hash + Eq + serde::Serialize, V: serde::Serialize",
1210            deserialize = "K: Hash + Eq + serde::Deserialize<'de>, V: serde::Deserialize<'de>"
1211        ))
1212    )]
1213    state: HashMap<K, DotMapValue<V>, DsonRandomState>,
1214}
1215
1216impl<K, V> FromIterator<(K, V)> for DotMap<K, V>
1217where
1218    K: Eq + Hash,
1219{
1220    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
1221        Self {
1222            state: HashMap::from_iter(iter.into_iter().map(|(key, value)| {
1223                (
1224                    key,
1225                    DotMapValue {
1226                        value,
1227                        dots: Default::default(),
1228                    },
1229                )
1230            })),
1231        }
1232    }
1233}
1234
1235impl<K, Q, V> Index<&Q> for DotMap<K, V>
1236where
1237    K: Eq + Hash + Borrow<Q>,
1238    Q: Eq + Hash + ?Sized,
1239{
1240    type Output = V;
1241
1242    fn index(&self, index: &Q) -> &Self::Output {
1243        &self.state.index(index).value
1244    }
1245}
1246
1247/// A value in a [`DotMap`], which includes the value itself and a cached set of dots.
1248#[derive(Clone, Default)]
1249#[cfg_attr(feature = "serde", derive(::serde::Deserialize, ::serde::Serialize))]
1250pub struct DotMapValue<V> {
1251    /// The value stored in the map.
1252    pub(super) value: V,
1253
1254    /// This field, if set, holds a cached version of `value.dots()`.
1255    ///
1256    /// Its purpose is to ensure that calls to `self.dots()` (or analogously `self.add_dots_to()`)
1257    /// do not need to recurse into `value.dots()`. This is hugely important for performance, as it
1258    /// ensures that calls to `.dots()` on map entries are (generally) quite cheap, rather than
1259    /// having to walk the entire subtree of objects. This, in turn, allows `.dots()` to be
1260    /// used to cheaply determine whether a subtree needs to be entered to discover changes based
1261    /// on some other `CausalContext`.
1262    ///
1263    /// The field is read primarily in `impl DotStore`, and updated in `impl DotStoreJoin` using
1264    /// the `on_dot_change` machinery.
1265    #[cfg_attr(feature = "serde", serde(skip))]
1266    pub(super) dots: Option<CausalContext>,
1267}
1268
1269impl<V: fmt::Debug> fmt::Debug for DotMapValue<V> {
1270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1271        f.debug_tuple(if self.dots.is_some() { "V" } else { "v" })
1272            .field(&self.value)
1273            .finish()
1274    }
1275}
1276
1277impl<V> PartialEq for DotMapValue<V>
1278where
1279    V: PartialEq,
1280{
1281    fn eq(&self, other: &Self) -> bool {
1282        self.value == other.value
1283    }
1284}
1285impl<V> Eq for DotMapValue<V> where V: Eq {}
1286
1287impl<V> DotStore for DotMapValue<V>
1288where
1289    V: DotStore,
1290{
1291    fn dots(&self) -> CausalContext {
1292        if let Some(dots) = &self.dots {
1293            debug_assert_eq!(dots, &self.value.dots());
1294            dots.clone()
1295        } else {
1296            self.value.dots()
1297        }
1298    }
1299
1300    fn add_dots_to(&self, other: &mut CausalContext) {
1301        if let Some(dots) = &self.dots {
1302            debug_assert_eq!(dots, &self.value.dots());
1303            other.union(dots);
1304        } else {
1305            self.value.add_dots_to(other);
1306        }
1307    }
1308
1309    fn is_bottom(&self) -> bool {
1310        self.value.is_bottom()
1311    }
1312
1313    fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
1314        Self {
1315            value: self.value.subset_for_inflation_from(frontier),
1316            dots: None,
1317        }
1318    }
1319}
1320
1321impl<S, V> DotStoreJoin<S> for DotMapValue<V>
1322where
1323    V: DotStoreJoin<S> + Default + fmt::Debug,
1324{
1325    fn join(
1326        (s1, ctx1): (Self, &CausalContext),
1327        (s2, ctx2): (Self, &CausalContext),
1328        on_dot_change: &mut dyn FnMut(DotChange),
1329        sentinel: &mut S,
1330    ) -> Result<Self, S::Error>
1331    where
1332        Self: Sized,
1333        S: crate::sentinel::Sentinel,
1334    {
1335        // NOTE! When making changes to this method, consider if corresponding
1336        // changes need to be done to ::dry_join as well!
1337
1338        let Self {
1339            value: s1,
1340            mut dots,
1341        } = s1;
1342        let Self { value: s2, dots: _ } = s2;
1343        let value = V::join(
1344            (s1, ctx1),
1345            (s2, ctx2),
1346            &mut |change| {
1347                if let Some(dots) = &mut dots {
1348                    match change {
1349                        DotChange::Add(dot) => {
1350                            dots.insert_dot(dot);
1351                        }
1352                        DotChange::Remove(dot) => {
1353                            dots.remove_dot(dot);
1354                        }
1355                    }
1356                }
1357                on_dot_change(change);
1358            },
1359            sentinel,
1360        )?;
1361        dots = Some(
1362            dots.map(|dots| {
1363                debug_assert_eq!(dots, value.dots(), "{value:?}");
1364                dots
1365            })
1366            .unwrap_or_else(|| value.dots()),
1367        );
1368        Ok(Self { value, dots })
1369    }
1370    fn dry_join(
1371        (s1, ctx1): (&Self, &CausalContext),
1372        (s2, ctx2): (&Self, &CausalContext),
1373        sentinel: &mut S,
1374    ) -> Result<DryJoinOutput, S::Error>
1375    where
1376        Self: Sized,
1377        S: Sentinel,
1378    {
1379        let Self { value: s1, dots: _ } = s1;
1380        let Self { value: s2, dots: _ } = s2;
1381        let value = V::dry_join((s1, ctx1), (s2, ctx2), sentinel)?;
1382        Ok(value)
1383    }
1384}
1385
1386impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for DotMap<K, V> {
1387    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1388        f.debug_map().entries(self.state.iter()).finish()
1389    }
1390}
1391
1392impl<K, V> Default for DotMap<K, V> {
1393    fn default() -> Self {
1394        Self {
1395            state: create_map(),
1396        }
1397    }
1398}
1399
1400impl<K, V> PartialEq for DotMap<K, V>
1401where
1402    K: Eq + Hash,
1403    V: PartialEq,
1404{
1405    fn eq(&self, other: &Self) -> bool {
1406        self.state.eq(&other.state)
1407    }
1408}
1409
1410impl<K, V> Eq for DotMap<K, V>
1411where
1412    K: Eq + Hash,
1413    V: Eq,
1414{
1415}
1416
1417impl<K, V> DotMap<K, V> {
1418    /// Constructs a [`DotMap`] with the given initial capacity.
1419    pub fn with_capacity(capacity: usize) -> Self {
1420        Self {
1421            state: create_map_with_capacity(capacity),
1422        }
1423    }
1424
1425    /// Produces an iterator over the map's keys and values.
1426    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&K, &V)> {
1427        self.state.iter().map(|(k, v)| (k, &v.value))
1428    }
1429
1430    // Insert the given key and value into the map.
1431    //
1432    // If the key is already present, its value is overwritten.
1433    //
1434    // Note, this is a low level operation. CRDT types should generally
1435    // not be manipulated directly by user code.
1436    #[doc(hidden)]
1437    pub fn insert(&mut self, key: K, value: V)
1438    where
1439        K: Eq + Hash,
1440    {
1441        self.state.insert(key, DotMapValue { value, dots: None });
1442    }
1443
1444    /// Produces a mutable iterator over the map's keys and values.
1445    ///
1446    /// Invalidates the dots cache for all the map's entries, so calling `.dots()` on this
1447    /// collection after invoking this method may be quite slow (it has to call `.dots()` on all
1448    /// the entries).
1449    pub fn iter_mut_and_invalidate(&mut self) -> impl ExactSizeIterator<Item = (&K, &mut V)> {
1450        self.state.iter_mut().map(|(k, v)| {
1451            // see `get_mut` for why we need this
1452            v.dots = None;
1453            (k, &mut v.value)
1454        })
1455    }
1456
1457    /// Produces an iterator over the map's keys.
1458    pub fn keys(&self) -> impl ExactSizeIterator<Item = &K> + '_ {
1459        self.state.keys()
1460    }
1461
1462    /// Produces an iterator over the map's values.
1463    pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
1464        self.state.values().map(|v| &v.value)
1465    }
1466
1467    /// Returns the number of keys in the map.
1468    pub fn len(&self) -> usize {
1469        self.state.len()
1470    }
1471
1472    /// Returns true if the map is empty.
1473    pub fn is_empty(&self) -> bool {
1474        self.state.is_empty()
1475    }
1476
1477    #[cfg(any(test, feature = "arbitrary"))]
1478    pub(crate) fn shrink(&self) -> Box<dyn Iterator<Item = Self>>
1479    where
1480        K: Clone + quickcheck::Arbitrary + Hash + Eq,
1481        V: Clone + quickcheck::Arbitrary,
1482    {
1483        Box::new(quickcheck::Arbitrary::shrink(&self.state).map(|state| Self { state }))
1484    }
1485}
1486
1487impl<K, V> DotMap<K, V>
1488where
1489    K: Hash + Eq,
1490{
1491    /// Retrieves the associated value, if any, for the given key.
1492    pub fn get<Q>(&self, key: &Q) -> Option<&V>
1493    where
1494        K: Borrow<Q>,
1495        Q: Hash + Eq + ?Sized,
1496    {
1497        self.state.get(key).map(|v| &v.value)
1498    }
1499
1500    /// Retrieves a mutable reference to the associated value, if any, for the given key.
1501    ///
1502    /// Invalidates the dots cache for the given map entry, so calling `.dots()` on this collection
1503    /// after invoking this method may be slower as it has to call `.dots()` on this entry to
1504    /// re-compute.
1505    pub fn get_mut_and_invalidate<Q>(&mut self, key: &Q) -> Option<&mut V>
1506    where
1507        K: Borrow<Q>,
1508        Q: Hash + Eq + ?Sized,
1509    {
1510        self.state.get_mut(key).map(|v| {
1511            // giving out `&mut v.value` ultimately permits changing `.value`, which in turn might
1512            // change its dots. if `v.value`'s dots change, then `v.dots` also needs to change to
1513            // match, but since we don't here know what that change (if any) might be, all we can
1514            // do is invalidate our cache so that it'll be re-computed at the next read.
1515            //
1516            // this problem (and solution) applies inductively: changing `v.value` changes
1517            // `v.dots`, which also changes `self.dots()`, which may in turn be cached somewhere
1518            // further up. however, since such a cache must _also_ have gone through `get_mut` (or
1519            // `iter_mut`), that cache must already have been invalidated for us to get here in the
1520            // first place.
1521            //
1522            // another option would be to somehow have a handle to those parents here (or
1523            // specifically, `&mut DotMapValue.dots`), but threading that through would be a
1524            // lifetime nightmare.
1525            v.dots = None;
1526            &mut v.value
1527        })
1528    }
1529
1530    /// Returns `true` if the given key has a value in this map.
1531    pub fn has<Q>(&self, key: &Q) -> bool
1532    where
1533        K: Borrow<Q>,
1534        Q: Hash + Eq + ?Sized,
1535    {
1536        self.state.contains_key(key)
1537    }
1538
1539    /// Associates the value with the given key.
1540    ///
1541    /// Returns the previous value if any.
1542    pub fn set(&mut self, key: K, value: V) -> Option<V>
1543    where
1544        V: DotStore,
1545    {
1546        let dots = Some(value.dots());
1547        self.state
1548            .insert(key, DotMapValue { value, dots })
1549            .map(|v| v.value)
1550    }
1551
1552    /// Removes the value with the given key.
1553    ///
1554    /// Returns the previous value if any.
1555    ///
1556    /// Be mindful that removing a key from a `DotMap` also changes its set of dots, but does _not_
1557    /// change the [`CausalContext`] in which the `DotMap` resides. As a result, removing a key in
1558    /// this way will make the CRDT that this `DotMap` represents imply the deletion of, not just
1559    /// the absence of, `key`.
1560    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
1561    where
1562        K: Borrow<Q>,
1563        Q: Hash + Eq + ?Sized,
1564    {
1565        self.state.remove(key).map(|v| v.value)
1566    }
1567
1568    /// Retains only key-value pairs for which `f` returns `true`.
1569    ///
1570    /// See [`DotMap::remove`] for the CRDT implications of removing keys in this way.
1571    ///
1572    /// Note that this does not invalidate the cache, even though it may become inaccurate. For an
1573    /// invalidating alternative, use [`Self::retain_and_invalidate`].
1574    pub fn retain<F>(&mut self, mut f: F)
1575    where
1576        F: FnMut(&K, &mut V) -> bool,
1577    {
1578        self.state.retain(|k, v| f(k, &mut v.value))
1579    }
1580
1581    /// Retains only key-value pairs for which `f` returns `true`.
1582    ///
1583    /// See [`DotMap::remove`] for the CRDT implications of removing keys in this way.
1584    ///
1585    /// Invalidates the dots cache for all entries, so calling `.dots()` on this collection
1586    /// after invoking this method may be slower as it has to call `.dots()` on each entry to
1587    /// re-compute.
1588    pub fn retain_and_invalidate<F>(&mut self, mut f: F)
1589    where
1590        F: FnMut(&K, &mut V) -> bool,
1591    {
1592        self.state.retain(|k, v| {
1593            // see comment in [`Self::get_mut_and_invalidate`]
1594            v.dots = None;
1595            f(k, &mut v.value)
1596        })
1597    }
1598}
1599
1600impl<K, V> DotStore for DotMap<K, V>
1601where
1602    K: Hash + Eq + fmt::Debug + Clone,
1603    V: DotStore,
1604{
1605    fn add_dots_to(&self, other: &mut CausalContext) {
1606        for v in self.values() {
1607            v.add_dots_to(other);
1608        }
1609    }
1610
1611    fn is_bottom(&self) -> bool {
1612        self.state.values().all(DotStore::is_bottom)
1613    }
1614
1615    fn subset_for_inflation_from(&self, frontier: &CausalContext) -> Self {
1616        let mut delta = Self {
1617            state: create_map_with_capacity(self.state.len()),
1618        };
1619
1620        for (k, v) in &self.state {
1621            let delta_v = v.subset_for_inflation_from(frontier);
1622            if !delta_v.is_bottom() {
1623                delta.state.insert(k.clone(), delta_v);
1624            }
1625        }
1626
1627        delta
1628    }
1629}
1630
1631impl<K, V, S> DotStoreJoin<S> for DotMap<K, V>
1632where
1633    K: Hash + Eq + fmt::Debug + Clone,
1634    V: DotStoreJoin<S> + Default + fmt::Debug,
1635    S: Visit<K> + KeySentinel,
1636    // needed for debug assertions of relevant_deletes optimization
1637    V: Clone + PartialEq,
1638{
1639    /// Formally (Equation 5):
1640    ///
1641    /// > (𝑚, 𝑐) ⊔ (𝑚′, 𝑐′) =
1642    /// >   (
1643    /// >       {𝑘 ↦ 𝑣(𝑘) | 𝑘 ∈ dom 𝑚 ∪ dom 𝑚′ ∧ 𝑣(𝑘) ≠ ⊥}
1644    /// >     , 𝑐 ∪ 𝑐′
1645    /// >   )
1646    /// >   where 𝑣(𝑘) = fst((𝑚(𝑘),𝑐) ⊔ (𝑚′(𝑘), 𝑐′))
1647    ///
1648    /// Informally:
1649    ///  - take the union of keys across the two stores:
1650    ///    - compute v as the join of the keys' values in the two maps (one may be ⊥)
1651    ///    - if v.store is ⊥, skip
1652    ///    - otherwise, include the k -> v.store mapping
1653    ///  - the resulting causal context is the union of the provided causal contexts
1654    fn join(
1655        (m1, cc1): (Self, &CausalContext),
1656        (mut m2, cc2): (Self, &CausalContext),
1657        on_dot_change: &mut dyn FnMut(DotChange),
1658        sentinel: &mut S,
1659    ) -> Result<Self, S::Error> {
1660        // NOTE! When making changes to this method, consider if corresponding
1661        // changes need to be done to ::dry_join as well!
1662
1663        // NOTE: the original code collects all the keys first, but that would require V: Clone
1664        // (and is unnecessarily inefficient), so we take a different approach that _should_ have
1665        // the exact same semantics: we iterate over m1 first, removing anything that's also in m2,
1666        // and then we iterate over what's left in m2.
1667        // TODO: we really shouldn't allocate a new Self here and then do a tonne of inserts,
1668        // since self can be quite large.
1669        let mut res_m = Self::with_capacity(m1.len().max(m2.len()));
1670        for (k, v1) in m1.state {
1671            sentinel.enter(&k)?;
1672            let v2 = m2.state.remove(&k).unwrap_or_default();
1673            if v2.is_bottom() {
1674                // TODO: We could maybe use something like a bloom filter here
1675                // rather than have to fully compare the dots. I think this would mean keeping a
1676                // bloom filter inside of `DotMapValue` that we also keep up to date, and then
1677                // taking the intersection of the bloom filters here, which should hopefully be
1678                // cheaper than what we currently do. An idea worth exploring at least!
1679                // NOTE: if v1 is also bottom (which _can_ happen), there is nothing to
1680                // delete, so there are definitely no relevant deletes.
1681                let relevant_deletes =
1682                    !v1.is_bottom() && v1.dots.as_ref().is_none_or(|dots| dots.any_dot_in(cc2));
1683                if !relevant_deletes {
1684                    // since v2 is bottom, if it contains any of the dots in v1, then that means it
1685                    // deletes _something_ under k, and so we need to recurse into k. otherwise, we
1686                    // can avoid recursing into the CRDT subtree under k entirely!
1687                    //
1688                    // in debug mode, validate that v1 indeed does not change as a result of
1689                    // joining with v2:
1690                    if cfg!(debug_assertions) {
1691                        let new_v = DotMapValue::<V>::join(
1692                            (v1.clone(), cc1),
1693                            (v2, cc2),
1694                            &mut |_| {},
1695                            // TODO: this should arguably be DummySentinel so that tests don't
1696                            // rely on the sentinel getting to see these operations just because
1697                            // the code is compiled in debug mode. however, doing so would require
1698                            // that we add `V: DotStoreJoin<DummySentinel>` as well, which would
1699                            // bubble up in a bunch of places.
1700                            sentinel,
1701                        )?;
1702
1703                        if v1.is_bottom() {
1704                            // there are multiple ways to be bottom, so don't check strict equality
1705                            assert!(new_v.is_bottom(), "{v1:?}\nis bottom, but not\n{new_v:?}");
1706                        } else {
1707                            assert_eq!(v1, new_v);
1708                        }
1709                    }
1710                    // NOTE: this also preserves v1.dots, ensuring that we never have to walk
1711                    // all of v1 as part of the join (which would be bad as v1 could be the entire
1712                    // CRDT state not just delta size)!
1713                    res_m.state.insert(k, v1);
1714                    sentinel.exit()?;
1715                    continue;
1716                }
1717            }
1718            let new_v = DotMapValue::<V>::join((v1, cc1), (v2, cc2), on_dot_change, sentinel)?;
1719            if !new_v.is_bottom() {
1720                // Value was in m1, still is alive, so this is an update (but possibly v2 == v1)
1721                // TODO(#2): is ds1's author authorized to write this value?
1722                // TODO(#2): if v2 is Some, is ds2's author authorized to write this value?
1723                res_m.state.insert(k, new_v);
1724            } else {
1725                // If a value was previously bottom, it wouldn't have been in m1.state, as we don't
1726                // store bottom values. This means the value is being removed by ds2.
1727                // TODO(#2): is ds2's author authorized to clear this value?
1728                sentinel.delete_key()?;
1729            }
1730            sentinel.exit()?;
1731        }
1732        // NOTE: this now only contains keys that weren't in m1
1733        for (k, v2) in m2.state {
1734            sentinel.enter(&k)?;
1735            // TODO: implement relevant_deletes optimization here too (v1 and v2 are just
1736            // swapped). didn't do that initially since m1 tends to be the "main document" and m2
1737            // the "delta", so it's more important we avoid exhaustively walking m1 than m2.
1738            //
1739            // NOTE: However, consider the need for sentinels to observe all inserts. If we do
1740            // the optimization described above, a sentinel will no longer observe the contents
1741            // of new trees that are added to the document. We could add an associated const
1742            // to the Sentinel trait, that describes if the optimization above is allowed or not.
1743            let v1 = DotMapValue::<V>::default();
1744            let new_v = DotMapValue::<V>::join((v1, cc1), (v2, cc2), on_dot_change, sentinel)?;
1745            if !new_v.is_bottom() {
1746                // TODO(#2): is ds2's author authorized to write this value?
1747                sentinel.create_key()?;
1748                res_m.state.insert(k, new_v);
1749            } else {
1750                // TODO(#2): is ds2's author authorized to clear this value?
1751                // NOTE: do we even care that ds2 is trying to remove a key that doesn't exist?
1752            }
1753            sentinel.exit()?;
1754        }
1755
1756        Ok(res_m)
1757    }
1758
1759    fn dry_join(
1760        (m1, cc1): (&Self, &CausalContext),
1761        (m2, cc2): (&Self, &CausalContext),
1762        sentinel: &mut S,
1763    ) -> Result<DryJoinOutput, S::Error> {
1764        // For explanation of this method, see comments in ::join(..).
1765
1766        let mut result = DryJoinOutput::bottom();
1767        for (k, v1) in m1.state.iter() {
1768            sentinel.enter(k)?;
1769            let default_v = Default::default();
1770            let v2: &DotMapValue<V> = m2.state.get(k).unwrap_or(&default_v);
1771            if v2.is_bottom() {
1772                let relevant_deletes =
1773                    !v1.is_bottom() && v1.dots.as_ref().is_none_or(|dots| dots.any_dot_in(cc2));
1774                if !relevant_deletes {
1775                    if cfg!(debug_assertions) {
1776                        let _new_v = DotMapValue::<V>::dry_join((v1, cc1), (v2, cc2), sentinel)?;
1777                    }
1778                    if !v1.is_bottom() {
1779                        result.set_is_not_bottom();
1780                    }
1781
1782                    sentinel.exit()?;
1783                    continue;
1784                }
1785            }
1786            let new_v = DotMapValue::<V>::dry_join((v1, cc1), (v2, cc2), sentinel)?;
1787            if !new_v.is_bottom() {
1788                result.set_is_not_bottom();
1789            } else {
1790                sentinel.delete_key()?;
1791            }
1792            sentinel.exit()?;
1793        }
1794
1795        for (k, v2) in m2.state.iter().filter(|(k, _v2)| !m1.has(k)) {
1796            sentinel.enter(k)?;
1797            let v1 = DotMapValue::<V>::default();
1798            let new_v = DotMapValue::<V>::dry_join((&v1, cc1), (v2, cc2), sentinel)?;
1799            if !new_v.is_bottom() {
1800                sentinel.create_key()?;
1801                result.set_is_not_bottom();
1802            }
1803            sentinel.exit()?;
1804        }
1805
1806        Ok(result)
1807    }
1808}
1809
1810impl<K, C> CausalDotStore<crate::crdts::ormap::OrMap<K, C>>
1811where
1812    K: Hash + Eq + fmt::Debug + Clone,
1813    C: crate::ExtensionType + Clone,
1814{
1815    /// Creates a new transaction for this store.
1816    ///
1817    /// The transaction exclusively borrows this store until committed.
1818    ///
1819    /// # Example
1820    ///
1821    /// ```
1822    /// # use dson::{CausalDotStore, Identifier, OrMap};
1823    /// let mut store = CausalDotStore::<OrMap<String>>::default();
1824    /// let id = Identifier::new(0, 0);
1825    /// let tx = store.transact(id);
1826    /// let delta = tx.commit();
1827    /// ```
1828    pub fn transact(
1829        &mut self,
1830        id: crate::Identifier,
1831    ) -> crate::transaction::MapTransaction<'_, K, C> {
1832        crate::transaction::MapTransaction::new(self, id)
1833    }
1834}
1835
1836#[cfg(any(test, feature = "arbitrary"))]
1837pub mod recording_sentinel;
1838
1839#[expect(clippy::wildcard_enum_match_arm)]
1840#[cfg(test)]
1841mod tests {
1842    use std::{
1843        collections::{BTreeMap, HashSet},
1844        num::NonZeroU64,
1845    };
1846
1847    const SEQ_1: NonZeroU64 = NonZeroU64::MIN;
1848    const SEQ_2: NonZeroU64 = NonZeroU64::MIN.saturating_add(1);
1849
1850    mod dotfun {
1851        use super::{super::*, *};
1852        use crate::sentinel::test::{NoChangeValidator, ValueCountingValidator};
1853
1854        #[test]
1855        fn basic() {
1856            let mut map = DotFun::default();
1857            assert!(map.is_empty());
1858            assert!(map.is_bottom());
1859            assert_eq!(map.len(), 0);
1860
1861            let dot = Dot::from(((0, 0), SEQ_1));
1862            assert!(!map.has(&dot));
1863            assert!(!map.dots().dot_in(dot));
1864            assert_eq!(map.get(&dot), None);
1865            assert_eq!(map.get_mut(&dot), None);
1866
1867            assert_eq!(map.set(dot, "foo"), None);
1868            assert!(map.has(&dot));
1869            assert!(map.dots().dot_in(dot));
1870            assert_eq!(map.get(&dot).copied(), Some("foo"));
1871            assert_eq!(map.get_mut(&dot).copied(), Some("foo"));
1872            assert!(!map.is_empty());
1873            assert!(!map.is_bottom());
1874            assert_eq!(map.len(), 1);
1875        }
1876
1877        #[test]
1878        fn join_bottoms() {
1879            let bottom = CausalDotStore::<DotFun<()>>::default();
1880
1881            // We don't expect to see anything created or deleted
1882            // joining bottom with bottom with no causal context
1883            // should produce bottom and an empty causal context
1884            let join = bottom
1885                .test_join(
1886                    &bottom,
1887                    &mut |_| unreachable!("no dots added or removed"),
1888                    &mut NoChangeValidator,
1889                )
1890                .unwrap();
1891            assert_eq!(join.context, Default::default());
1892            assert!(join.store.is_bottom());
1893        }
1894
1895        #[test]
1896        fn join_with_bottom() {
1897            let mut validator = ValueCountingValidator::default();
1898
1899            let mut ds = CausalDotStore::<DotFun<_>>::default();
1900            let bottom = CausalDotStore::<DotFun<_>>::default();
1901
1902            // joining non-bottom x with bottom should produce x (no changes are observed)
1903            let dot = Dot::from(((0, 0), SEQ_1));
1904            ds.store.set(dot, ());
1905            ds.context.insert_next_dot(dot);
1906            let join = ds
1907                .test_join(
1908                    &bottom,
1909                    &mut |_| unreachable!("no dots added or removed"),
1910                    &mut validator,
1911                )
1912                .unwrap();
1913            assert_eq!(join.context, ds.context);
1914            assert!(!join.store.is_bottom());
1915            assert_eq!(join.store.get(&dot).copied(), Some(()));
1916            assert!(validator.added.is_empty()); // we started with something and nothing was added
1917            assert!(validator.removed.is_empty());
1918
1919            // joining bottom with non-bottom x should also produce x (but a change is observed)
1920            let join = bottom
1921                .test_join(
1922                    &ds,
1923                    &mut |change| assert_eq!(change, DotChange::Add(dot)),
1924                    &mut validator,
1925                )
1926                .unwrap();
1927            assert_eq!(join.context, ds.context);
1928            assert!(!join.store.is_bottom());
1929            assert_eq!(join.store.get(&dot).copied(), Some(()));
1930            assert_eq!(validator.added.len(), 1); // we started with nothing and something was added
1931            assert!(validator.removed.is_empty());
1932        }
1933
1934        #[test]
1935        fn join_idempotecy() {
1936            let mut ds = CausalDotStore::<DotFun<_>>::default();
1937            let dot = Dot::from(((0, 0), SEQ_1));
1938            ds.store.set(dot, ());
1939            ds.context.insert_next_dot(dot);
1940            let join = ds
1941                .test_join(
1942                    &ds,
1943                    &mut |_| unreachable!("self-join means no dot changes"),
1944                    &mut NoChangeValidator,
1945                )
1946                .unwrap();
1947            assert_eq!(join.context, ds.context);
1948            assert!(!join.store.is_bottom());
1949            assert_eq!(join.store.get(&dot).copied(), Some(()));
1950        }
1951
1952        #[test]
1953        fn join_keeps_independent() {
1954            let mut validator = ValueCountingValidator::default();
1955
1956            let mut ds1 = CausalDotStore::<DotFun<_>>::default();
1957            let mut ds2 = CausalDotStore::<DotFun<_>>::default();
1958
1959            let dot1 = Dot::from(((0, 0), SEQ_1));
1960            ds1.store.set(dot1, "dot1");
1961            ds1.context.insert_next_dot(dot1);
1962            let dot2 = Dot::from(((1, 0), SEQ_1));
1963            ds2.store.set(dot2, "dot2");
1964            ds2.context.insert_next_dot(dot2);
1965
1966            let mut expected_context = ds1.context.clone();
1967            expected_context.union(&ds2.context);
1968
1969            let join = ds1
1970                .test_join(
1971                    &ds2,
1972                    &mut |change| assert_eq!(change, DotChange::Add(dot2)),
1973                    &mut validator,
1974                )
1975                .unwrap();
1976            assert_eq!(validator.added, BTreeMap::from([("dot2", 1)]));
1977            assert!(validator.removed.is_empty());
1978
1979            assert_eq!(join.context, expected_context);
1980            assert!(!join.store.is_bottom());
1981            assert_eq!(join.store.get(&dot1).copied(), Some("dot1"));
1982            assert_eq!(join.store.get(&dot2).copied(), Some("dot2"));
1983        }
1984
1985        #[test]
1986        fn join_drops_removed() {
1987            let mut validator = ValueCountingValidator::default();
1988
1989            let mut ds1 = CausalDotStore::<DotFun<_>>::default();
1990            let mut ds2 = CausalDotStore::<DotFun<_>>::default();
1991
1992            let dot1 = Dot::from(((0, 0), SEQ_1));
1993            ds1.store.set(dot1, "dot1");
1994            ds1.context.insert_next_dot(dot1);
1995            let dot2 = Dot::from(((1, 0), SEQ_1));
1996            ds2.store.set(dot2, "dot2");
1997            ds2.context.insert_next_dot(dot2);
1998
1999            // make it so that ds1 has seen dot2, but does not have a value for it,
2000            // which implies that ds1 explicitly removed dot2.
2001            ds1.context.insert_next_dot(dot2);
2002
2003            let mut expected_context = ds1.context.clone();
2004            expected_context.union(&ds2.context);
2005
2006            // also check that the join is symmetrical
2007            // (modulo the semantics of on_dot_change and sentinels)
2008            let join = ds1
2009                .test_join(
2010                    &ds2,
2011                    &mut |_| unreachable!("ds1 has seen all of ds2, so no dot changes"),
2012                    &mut validator,
2013                )
2014                .unwrap();
2015
2016            assert_eq!(join.context, expected_context);
2017            assert!(!join.store.is_bottom());
2018            assert_eq!(join.store.get(&dot1).copied(), Some("dot1"));
2019            assert_eq!(join.store.get(&dot2), None);
2020
2021            let mut added = HashSet::new();
2022            let mut removed = HashSet::new();
2023            let join = ds2
2024                .test_join(
2025                    &ds1,
2026                    &mut |change| match change {
2027                        DotChange::Add(d) if d == dot1 => {
2028                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2029                        }
2030                        DotChange::Remove(d) if d == dot2 => {
2031                            assert!(removed.insert(d), "on_dot_change removed {d:?} twice");
2032                        }
2033                        diff => unreachable!("{diff:?}"),
2034                    },
2035                    &mut validator,
2036                )
2037                .unwrap();
2038
2039            assert_eq!(join.context, expected_context);
2040            assert!(!join.store.is_bottom());
2041            assert_eq!(join.store.get(&dot1).copied(), Some("dot1"));
2042            assert_eq!(join.store.get(&dot2), None);
2043
2044            assert_eq!(added, HashSet::from_iter([dot1]));
2045            assert_eq!(removed, HashSet::from_iter([dot2]));
2046
2047            assert_eq!(validator.added, BTreeMap::from([("dot1", 1)]));
2048            assert_eq!(validator.removed, BTreeMap::from([("dot2", 1)]));
2049        }
2050    }
2051
2052    mod dotfunmap {
2053        use super::{super::*, *};
2054        use crate::sentinel::{DummySentinel, test::NoChangeValidator};
2055
2056        #[test]
2057        fn basic() {
2058            let mut map = DotFunMap::default();
2059            assert!(map.is_empty());
2060            assert!(map.is_bottom());
2061            assert_eq!(map.len(), 0);
2062
2063            let key = Dot::from(((9, 0), SEQ_1));
2064            let dot = Dot::from(((0, 0), SEQ_1));
2065            assert!(!map.has(&key));
2066            assert!(!map.dots().dot_in(dot));
2067            assert_eq!(map.get(&key), None);
2068            assert_eq!(map.get_mut(&key), None);
2069
2070            assert_eq!(map.set(key, DotFun::default()), None);
2071            assert!(map.has(&key));
2072            assert_ne!(map.get(&key), None);
2073            assert_ne!(map.get_mut(&key), None);
2074            assert!(!map.is_empty());
2075            assert!(!map.is_bottom());
2076            assert_eq!(map.len(), 1);
2077
2078            // since we inserted an empty DotStore, there are still no dots:
2079            assert!(!map.dots().dot_in(dot));
2080            // until we insert one:
2081            map.get_mut(&key).unwrap().set(dot, "bar");
2082            assert!(map.dots().dot_in(dot));
2083        }
2084
2085        #[test]
2086        fn join_bottoms() {
2087            let bottom = CausalDotStore::<DotFunMap<DotFun<()>>>::default();
2088
2089            // joining bottom with bottom with no causal context
2090            // should produce bottom and an empty causal context
2091            let join = bottom
2092                .test_join(
2093                    &bottom,
2094                    &mut |_| unreachable!("no dots added or removed"),
2095                    &mut NoChangeValidator,
2096                )
2097                .unwrap();
2098            assert_eq!(join.context, Default::default());
2099            assert!(join.store.is_bottom());
2100        }
2101
2102        #[test]
2103        fn join_with_bottom() {
2104            let mut ds = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2105            let bottom = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2106
2107            // joining non-bottom x with bottom should produce x
2108            let key = Dot::from(((9, 0), SEQ_1));
2109            let dot = Dot::from(((0, 0), SEQ_1));
2110            let mut v = DotFun::default();
2111            v.set(dot, ());
2112            ds.store.set(key, v.clone());
2113            ds.context.insert_next_dot(dot);
2114            let join = ds
2115                .test_join(
2116                    &bottom,
2117                    &mut |_| unreachable!("no dots added or removed"),
2118                    &mut DummySentinel,
2119                )
2120                .unwrap();
2121            assert_eq!(join.context, ds.context);
2122            assert!(!join.store.is_bottom());
2123            assert_eq!(join.store.get(&key), Some(&v));
2124
2125            // and that should be symmetric
2126            // (modulo the semantics of on_dot_change and sentinels)
2127            let mut added = HashSet::new();
2128            let join = bottom
2129                .test_join(
2130                    &ds,
2131                    &mut |change| match change {
2132                        DotChange::Add(d) if [key, dot].contains(&d) => {
2133                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2134                        }
2135                        diff => unreachable!("{diff:?}"),
2136                    },
2137                    &mut DummySentinel,
2138                )
2139                .unwrap();
2140            assert_eq!(join.context, ds.context);
2141            assert!(!join.store.is_bottom());
2142            assert_eq!(join.store.get(&key), Some(&v));
2143            assert_eq!(added, HashSet::from_iter([key, dot]));
2144        }
2145
2146        #[test]
2147        fn join_idempotecy() {
2148            let mut ds = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2149
2150            let key = Dot::from(((9, 0), SEQ_1));
2151            let dot = Dot::from(((0, 0), SEQ_1));
2152            let mut v = DotFun::default();
2153            v.set(dot, ());
2154            ds.store.set(key, v.clone());
2155            ds.context.insert_next_dot(dot);
2156
2157            let join = ds
2158                .test_join(
2159                    &ds,
2160                    &mut |_| unreachable!("self-join means no dot changes"),
2161                    &mut DummySentinel,
2162                )
2163                .unwrap();
2164            assert_eq!(join.context, ds.context);
2165            assert!(!join.store.is_bottom());
2166            assert_eq!(join.store.get(&key), Some(&v));
2167        }
2168
2169        #[test]
2170        fn join_keeps_independent() {
2171            let mut ds1 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2172            let mut ds2 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2173
2174            let key1 = Dot::from(((9, 0), SEQ_1));
2175            let dot1 = Dot::from(((0, 0), SEQ_1));
2176            let mut v1 = DotFun::default();
2177            v1.set(dot1, "dot1");
2178            ds1.store.set(key1, v1.clone());
2179            ds1.context.insert_next_dot(dot1);
2180            let key2 = Dot::from(((8, 0), SEQ_1));
2181            let dot2 = Dot::from(((1, 0), SEQ_1));
2182            let mut v2 = DotFun::default();
2183            v2.set(dot2, "dot2");
2184            ds2.store.set(key2, v2.clone());
2185            ds2.context.insert_next_dot(dot2);
2186
2187            let mut expected_context = ds1.context.clone();
2188            expected_context.union(&ds2.context);
2189
2190            let mut changes = HashMap::new();
2191            let join = ds1
2192                .test_join(
2193                    &ds2,
2194                    &mut |change| {
2195                        *changes.entry(change).or_default() += 1;
2196                    },
2197                    &mut DummySentinel,
2198                )
2199                .unwrap();
2200
2201            assert_eq!(join.context, expected_context);
2202            assert!(!join.store.is_bottom());
2203            assert_eq!(join.store.get(&key1), Some(&v1));
2204            assert_eq!(join.store.get(&key2), Some(&v2));
2205            assert_eq!(
2206                changes,
2207                HashMap::from_iter([(DotChange::Add(dot2), 1), (DotChange::Add(key2), 1)])
2208            );
2209        }
2210
2211        #[test]
2212        fn join_drops_removed() {
2213            let mut ds1 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2214            let mut ds2 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2215
2216            let key1 = Dot::from(((9, 0), SEQ_1));
2217            let dot1 = Dot::from(((0, 0), SEQ_1));
2218            let mut v1 = DotFun::default();
2219            v1.set(dot1, "dot1");
2220            ds1.store.set(key1, v1.clone());
2221            ds1.context.insert_next_dot(dot1);
2222            let key2 = Dot::from(((8, 0), SEQ_1));
2223            let dot2 = Dot::from(((1, 0), SEQ_1));
2224            let mut v2 = DotFun::default();
2225            v2.set(dot2, "dot2");
2226            ds2.store.set(key2, v2.clone());
2227            ds2.context.insert_next_dot(dot2);
2228
2229            // make it so that ds1 has seen dot2, but does not have a value for key1,
2230            // which implies that ds1 explicitly removed key1.
2231            ds1.context.insert_next_dot(key2);
2232            ds1.context.insert_next_dot(dot2);
2233
2234            let mut expected_context = ds1.context.clone();
2235            expected_context.union(&ds2.context);
2236
2237            // also check that the join is symmetrical
2238            // (modulo the semantics of on_dot_change and sentinels)
2239            let join = ds1
2240                .test_join(
2241                    &ds2,
2242                    &mut |_| unreachable!("ds1 has seen all of ds2, so no dot changes"),
2243                    &mut DummySentinel,
2244                )
2245                .unwrap();
2246
2247            assert_eq!(join.context, expected_context);
2248            assert!(!join.store.is_bottom());
2249            assert_eq!(join.store.get(&key1), Some(&v1));
2250            assert_eq!(join.store.get(&key2), None);
2251
2252            let mut added = HashSet::new();
2253            let mut removed = HashSet::new();
2254            let join = ds2
2255                .test_join(
2256                    &ds1,
2257                    &mut |change| match change {
2258                        DotChange::Add(d) if [dot1, key1].contains(&d) => {
2259                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2260                        }
2261                        DotChange::Remove(d) if [dot2, key2].contains(&d) => {
2262                            assert!(removed.insert(d), "on_dot_change removed {d:?} twice");
2263                        }
2264                        diff => unreachable!("{diff:?}"),
2265                    },
2266                    &mut DummySentinel,
2267                )
2268                .unwrap();
2269
2270            assert_eq!(join.context, expected_context);
2271            assert!(!join.store.is_bottom());
2272            assert_eq!(join.store.get(&key1), Some(&v1));
2273            assert_eq!(join.store.get(&key2), None);
2274            assert_eq!(added, HashSet::from_iter([dot1, key1]));
2275            assert_eq!(removed, HashSet::from_iter([dot2, key2]));
2276        }
2277
2278        #[test]
2279        fn nested_join() {
2280            let mut ds1 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2281            let mut ds2 = CausalDotStore::<DotFunMap<DotFun<_>>>::default();
2282
2283            // a single shared key this time so the join will need to join the DotFuns
2284            let key = Dot::from(((9, 0), SEQ_1));
2285
2286            let dot1 = Dot::from(((0, 0), SEQ_1));
2287            let mut v1 = DotFun::default();
2288            v1.set(dot1, "dot1");
2289            ds1.store.set(key, v1.clone());
2290            ds1.context.insert_next_dot(dot1);
2291
2292            // since we're testing nested join here too, make sure that join is _entirely_ obvious
2293            // by make it so that ds1 has seen dot2 but not repeated it, which implies that dot2 is
2294            // removed. we also add an extra value in ds2 for extra spice.
2295            let dot2 = Dot::from(((1, 0), SEQ_1));
2296            let dot3 = Dot::from(((1, 0), SEQ_2));
2297            let mut v2 = DotFun::default();
2298            v2.set(dot2, "dot2");
2299            v2.set(dot3, "dot3");
2300            ds2.store.set(key, v2.clone());
2301            ds2.context.extend([dot2, dot3]);
2302            ds1.context.insert_next_dot(dot2);
2303
2304            let mut expected_context = ds1.context.clone();
2305            expected_context.union(&ds2.context);
2306
2307            let expected_v = DotFun::join(
2308                (v1, &ds1.context),
2309                (v2, &ds2.context),
2310                &mut |change| assert_eq!(change, DotChange::Add(dot3)),
2311                &mut DummySentinel,
2312            )
2313            .unwrap();
2314
2315            // also check that the join is symmetrical
2316            // (modulo the semantics of on_dot_change and sentinels)
2317            let mut added = HashSet::new();
2318            let mut removed = HashSet::new();
2319            let join = ds1
2320                .test_join(
2321                    &ds2,
2322                    &mut |change| match change {
2323                        DotChange::Add(d) if d == dot3 => {
2324                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2325                        }
2326                        diff => unreachable!("{diff:?}"),
2327                    },
2328                    &mut DummySentinel,
2329                )
2330                .unwrap();
2331
2332            assert_eq!(join.context, expected_context);
2333            assert!(!join.store.is_bottom());
2334            assert_eq!(join.store.get(&key), Some(&expected_v));
2335            assert_eq!(added, HashSet::from_iter([dot3]));
2336            assert_eq!(removed, HashSet::from_iter([]));
2337
2338            added.clear();
2339            removed.clear();
2340            let join = ds2
2341                .test_join(
2342                    &ds1,
2343                    &mut |change| match change {
2344                        DotChange::Add(d) if d == dot1 => {
2345                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2346                        }
2347                        DotChange::Remove(d) if d == dot2 => {
2348                            assert!(removed.insert(d), "on_dot_change removed {d:?} twice");
2349                        }
2350                        diff => unreachable!("{diff:?}"),
2351                    },
2352                    &mut DummySentinel,
2353                )
2354                .unwrap();
2355
2356            assert_eq!(join.context, expected_context);
2357            assert!(!join.store.is_bottom());
2358            assert_eq!(join.store.get(&key), Some(&expected_v));
2359            assert_eq!(added, HashSet::from_iter([dot1]));
2360            assert_eq!(removed, HashSet::from_iter([dot2]));
2361        }
2362    }
2363
2364    mod dotmap {
2365        use super::{super::*, *};
2366        use crate::sentinel::test::{
2367            KeyCountingValidator, NoChangeValidator, ValueCountingValidator,
2368        };
2369
2370        #[test]
2371        fn basic() {
2372            let mut map = DotMap::default();
2373            assert!(map.is_empty());
2374            assert!(map.is_bottom());
2375            assert_eq!(map.len(), 0);
2376
2377            let key = "foo";
2378            let dot = Dot::from(((0, 0), SEQ_1));
2379            assert!(!map.has(key));
2380            assert!(!map.dots().dot_in(dot));
2381            assert_eq!(map.get(key), None);
2382            assert_eq!(map.get_mut_and_invalidate(key), None);
2383
2384            assert_eq!(map.set(key, DotFun::default()), None);
2385            assert!(map.has(key));
2386            assert_ne!(map.get(key), None);
2387            assert_ne!(map.get_mut_and_invalidate(key), None);
2388            assert!(!map.is_empty());
2389            assert!(map.is_bottom(), "map is bottom since all values are bottom");
2390            assert_eq!(map.len(), 1);
2391
2392            // since we inserted an empty DotStore, there are still no dots:
2393            assert!(!map.dots().dot_in(dot));
2394            // until we insert one:
2395            map.get_mut_and_invalidate(key).unwrap().set(dot, "bar");
2396            assert!(map.dots().dot_in(dot));
2397            assert!(!map.is_bottom());
2398        }
2399
2400        #[test]
2401        fn join_bottoms() {
2402            let bottom = CausalDotStore::<DotMap<(), DotFun<()>>>::default();
2403
2404            // joining bottom with bottom with no causal context
2405            // should produce bottom and an empty causal context
2406            let join = bottom
2407                .test_join(
2408                    &bottom,
2409                    &mut |_| unreachable!("no dots added or removed"),
2410                    &mut NoChangeValidator,
2411                )
2412                .unwrap();
2413            assert_eq!(join.context, Default::default());
2414            assert!(join.store.is_bottom());
2415        }
2416
2417        #[test]
2418        fn join_with_bottom() {
2419            let mut validator = KeyCountingValidator::default();
2420
2421            let mut ds = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2422            let bottom = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2423
2424            // joining non-bottom x with bottom should produce x (and no changes are observed)
2425            let key = "foo";
2426            let dot = Dot::from(((0, 0), SEQ_1));
2427            let mut v = DotFun::default();
2428            v.set(dot, ());
2429            ds.store.set(key, v.clone());
2430            ds.context.insert_next_dot(dot);
2431            let join = ds
2432                .test_join(
2433                    &bottom,
2434                    &mut |_| unreachable!("no dots added or removed"),
2435                    &mut validator,
2436                )
2437                .unwrap();
2438            assert_eq!(join.context, ds.context);
2439            assert!(!join.store.is_bottom());
2440            assert_eq!(join.store.get(key), Some(&v));
2441            assert_eq!(validator.added, 0);
2442            assert_eq!(validator.removed, 0);
2443
2444            // joining bottom with non-bottom x should produce x (and a change is observed)
2445            let join = bottom
2446                .test_join(
2447                    &ds,
2448                    &mut |change| assert_eq!(change, DotChange::Add(dot)),
2449                    &mut validator,
2450                )
2451                .unwrap();
2452            assert_eq!(join.context, ds.context);
2453            assert!(!join.store.is_bottom());
2454            assert_eq!(join.store.get(key), Some(&v));
2455            assert_eq!(validator.added, 1);
2456            assert_eq!(validator.removed, 0);
2457        }
2458
2459        #[test]
2460        fn join_idempotecy() {
2461            let mut ds = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2462
2463            let key = "foo";
2464            let dot = Dot::from(((0, 0), SEQ_1));
2465            let mut v = DotFun::default();
2466            v.set(dot, ());
2467            ds.store.set(key, v.clone());
2468            ds.context.insert_next_dot(dot);
2469
2470            let join = ds
2471                .test_join(
2472                    &ds,
2473                    &mut |_| unreachable!("self-join means no dot changes"),
2474                    &mut NoChangeValidator,
2475                )
2476                .unwrap();
2477            assert_eq!(join.context, ds.context);
2478            assert!(!join.store.is_bottom());
2479            assert_eq!(join.store.get(key), Some(&v));
2480        }
2481
2482        #[test]
2483        fn join_keeps_independent() {
2484            let mut validator = KeyCountingValidator::default();
2485
2486            let mut ds1 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2487            let mut ds2 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2488
2489            let key1 = "foo";
2490            let dot1 = Dot::from(((0, 0), SEQ_1));
2491            let mut v1 = DotFun::default();
2492            v1.set(dot1, "dot1");
2493            ds1.store.set(key1, v1.clone());
2494            ds1.context.insert_next_dot(dot1);
2495            let key2 = "bar";
2496            let dot2 = Dot::from(((1, 0), SEQ_1));
2497            let mut v2 = DotFun::default();
2498            v2.set(dot2, "dot2");
2499            ds2.store.set(key2, v2.clone());
2500            ds2.context.insert_next_dot(dot2);
2501
2502            let mut expected_context = ds1.context.clone();
2503            expected_context.union(&ds2.context);
2504
2505            let join = ds1
2506                .test_join(
2507                    &ds2,
2508                    &mut |change| assert_eq!(change, DotChange::Add(dot2)),
2509                    &mut validator,
2510                )
2511                .unwrap();
2512            assert_eq!(validator.added, 1);
2513            assert_eq!(validator.removed, 0);
2514
2515            assert_eq!(join.context, expected_context);
2516            assert!(!join.store.is_bottom());
2517            assert_eq!(join.store.get(key1), Some(&v1));
2518            assert_eq!(join.store.get(key2), Some(&v2));
2519        }
2520
2521        #[test]
2522        fn join_drops_removed() {
2523            let mut validator = KeyCountingValidator::default();
2524
2525            let mut ds1 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2526            let mut ds2 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2527
2528            let key1 = "foo";
2529            let dot1 = Dot::from(((0, 0), SEQ_1));
2530            let mut v1 = DotFun::default();
2531            v1.set(dot1, "dot1");
2532            ds1.store.set(key1, v1.clone());
2533            ds1.context.insert_next_dot(dot1);
2534            let key2 = "bar";
2535            let dot2 = Dot::from(((1, 0), SEQ_1));
2536            let mut v2 = DotFun::default();
2537            v2.set(dot2, "dot2");
2538            ds2.store.set(key2, v2.clone());
2539            ds2.context.insert_next_dot(dot2);
2540
2541            // make it so that ds1 has seen dot2, but does not have a value for key1,
2542            // which implies that ds1 explicitly removed key1.
2543            ds1.context.insert_next_dot(dot2);
2544
2545            let mut expected_context = ds1.context.clone();
2546            expected_context.union(&ds2.context);
2547
2548            // also check that the join is symmetrical
2549            let join = ds1
2550                .test_join(
2551                    &ds2,
2552                    &mut |_| unreachable!("ds1 has seen all of ds2, so no dot changes"),
2553                    &mut validator,
2554                )
2555                .unwrap();
2556
2557            assert_eq!(join.context, expected_context);
2558            assert!(!join.store.is_bottom());
2559            assert_eq!(join.store.get(key1), Some(&v1));
2560            assert_eq!(join.store.get(key2), None);
2561
2562            let mut added = HashSet::new();
2563            let mut removed = HashSet::new();
2564            let join = ds2
2565                .test_join(
2566                    &ds1,
2567                    &mut |change| match change {
2568                        DotChange::Add(d) if d == dot1 => {
2569                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2570                        }
2571                        DotChange::Remove(d) if d == dot2 => {
2572                            assert!(removed.insert(d), "on_dot_change removed {d:?} twice");
2573                        }
2574                        diff => unreachable!("{diff:?}"),
2575                    },
2576                    &mut validator,
2577                )
2578                .unwrap();
2579
2580            assert_eq!(join.context, expected_context);
2581            assert!(!join.store.is_bottom());
2582            assert_eq!(join.store.get(key1), Some(&v1));
2583            assert_eq!(join.store.get(key2), None);
2584
2585            assert_eq!(added, HashSet::from_iter([dot1]));
2586            assert_eq!(removed, HashSet::from_iter([dot2]));
2587
2588            assert_eq!(validator.added, 1);
2589            assert_eq!(validator.removed, 1);
2590        }
2591
2592        #[test]
2593        fn nested_join() {
2594            let mut validator = ValueCountingValidator::default();
2595
2596            let mut ds1 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2597            let mut ds2 = CausalDotStore::<DotMap<_, DotFun<_>>>::default();
2598
2599            // a single shared key this time so the join will need to join the DotFuns
2600            let key = "foo";
2601
2602            let dot1 = Dot::from(((0, 0), SEQ_1));
2603            let mut v1 = DotFun::default();
2604            v1.set(dot1, "dot1");
2605            ds1.store.set(key, v1.clone());
2606            ds1.context.insert_next_dot(dot1);
2607
2608            // since we're testing nested join here too, make sure that join is _entirely_ obvious
2609            // by make it so that ds1 has seen dot2 but not repeated it, which implies that dot2 is
2610            // removed. we also add an extra value in ds2 for extra spice.
2611            let dot2 = Dot::from(((1, 0), SEQ_1));
2612            let dot3 = Dot::from(((1, 0), SEQ_2));
2613            let mut v2 = DotFun::default();
2614            v2.set(dot2, "dot2");
2615            v2.set(dot3, "dot3");
2616            ds2.store.set(key, v2.clone());
2617            ds2.context.extend([dot2, dot3]);
2618            ds1.context.insert_next_dot(dot2);
2619
2620            let mut expected_context = ds1.context.clone();
2621            expected_context.union(&ds2.context);
2622
2623            let expected_v = DotFun::join(
2624                (v1, &ds1.context),
2625                (v2, &ds2.context),
2626                &mut |_| {},
2627                &mut validator,
2628            )
2629            .unwrap();
2630            assert_eq!(validator.added, BTreeMap::from([("dot3", 1)]));
2631            assert!(validator.removed.is_empty());
2632
2633            // also check that the join is symmetrical
2634            // (modulo the semantics of on_dot_change and sentinels)
2635            let mut added = HashSet::new();
2636            let mut removed = HashSet::new();
2637            let join = ds1
2638                .test_join(
2639                    &ds2,
2640                    &mut |change| match change {
2641                        DotChange::Add(d) if d == dot3 => {
2642                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2643                        }
2644                        diff => unreachable!("{diff:?}"),
2645                    },
2646                    &mut validator,
2647                )
2648                .unwrap();
2649
2650            assert_eq!(join.context, expected_context);
2651            assert!(!join.store.is_bottom());
2652            assert_eq!(join.store.get(key), Some(&expected_v));
2653            assert_eq!(added, HashSet::from_iter([dot3]));
2654            assert_eq!(removed, HashSet::from_iter([]));
2655
2656            added.clear();
2657            removed.clear();
2658            let join = ds2
2659                .test_join(
2660                    &ds1,
2661                    &mut |change| match change {
2662                        DotChange::Add(d) if d == dot1 => {
2663                            assert!(added.insert(d), "on_dot_change added {d:?} twice");
2664                        }
2665                        DotChange::Remove(d) if d == dot2 => {
2666                            assert!(removed.insert(d), "on_dot_change removed {d:?} twice");
2667                        }
2668                        diff => unreachable!("{diff:?}"),
2669                    },
2670                    &mut validator,
2671                )
2672                .unwrap();
2673
2674            assert_eq!(join.context, expected_context);
2675            assert!(!join.store.is_bottom());
2676            assert_eq!(join.store.get(key), Some(&expected_v));
2677
2678            assert_eq!(added, HashSet::from_iter([dot1]));
2679            assert_eq!(removed, HashSet::from_iter([dot2]));
2680
2681            assert_eq!(validator.added, BTreeMap::from([("dot1", 1), ("dot3", 2)]));
2682            assert_eq!(validator.removed, BTreeMap::from([("dot2", 1)]));
2683        }
2684    }
2685
2686    #[test]
2687    fn causal_dot_store_transact() {
2688        use crate::{CausalDotStore, Identifier, OrMap};
2689        let mut store = CausalDotStore::<OrMap<String>>::default();
2690        let id = Identifier::new(0, 0);
2691        let tx = store.transact(id);
2692        let _delta = tx.commit();
2693    }
2694}