Skip to main content

query_lang/
revision.rs

1//! The database version counter that drives incremental validation.
2
3use core::fmt;
4
5/// A monotonic version stamp for the database.
6///
7/// Every time an input changes, the [`Database`](crate::Database) advances its
8/// revision by one. The engine never compares values to decide whether a cached
9/// query is still good — it compares revisions, which is a single integer compare
10/// regardless of how large the cached value is. Two stamps live on every memoized
11/// query: the revision it was last *verified* at, and the revision its value last
12/// *changed* at. A query is still valid when none of its dependencies changed
13/// after it was last verified, and that whole judgement reduces to `>` on
14/// `Revision`.
15///
16/// Revisions are opaque and ordered: newer revisions compare greater than older
17/// ones. The concrete number is exposed through [`as_u64`](Self::as_u64) for
18/// logging and tests, but carries no meaning beyond its order.
19///
20/// # Examples
21///
22/// The revision advances only when an input actually changes value:
23///
24/// ```
25/// use query_lang::{Database, System, QueryError};
26///
27/// struct S;
28/// impl System for S {
29///     type Key = u32;
30///     type Value = u32;
31///     fn compute(&self, _db: &Database<Self>, key: &u32) -> Result<u32, QueryError> {
32///         Ok(*key)
33///     }
34/// }
35///
36/// let mut db = Database::new(S);
37/// let start = db.revision();
38///
39/// db.set(1, 10);
40/// assert!(db.revision() > start);          // a new input advanced the clock
41///
42/// let after_first = db.revision();
43/// db.set(1, 10);                            // same value — no real change
44/// assert_eq!(db.revision(), after_first);   // the clock did not move
45/// ```
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48pub struct Revision(u64);
49
50impl Revision {
51    /// The revision of a freshly created database, before any input is set.
52    pub(crate) const START: Revision = Revision(0);
53
54    /// The next revision after this one.
55    ///
56    /// Saturating rather than wrapping: after `u64::MAX` mutations the counter
57    /// stops advancing instead of wrapping back to an older-looking value, which
58    /// would silently mark every cached query as current. Reaching that bound
59    /// requires more than eighteen quintillion input mutations in one process, so
60    /// in practice the saturation never fires — it exists so the arithmetic can
61    /// never overflow or panic.
62    pub(crate) fn next(self) -> Revision {
63        Revision(self.0.saturating_add(1))
64    }
65
66    /// The underlying counter value.
67    ///
68    /// Useful for logging and assertions. The number has no meaning on its own;
69    /// only the order between two revisions is significant.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// use query_lang::{Database, System, QueryError};
75    ///
76    /// struct S;
77    /// impl System for S {
78    ///     type Key = u32;
79    ///     type Value = u32;
80    ///     fn compute(&self, _db: &Database<Self>, k: &u32) -> Result<u32, QueryError> { Ok(*k) }
81    /// }
82    ///
83    /// let mut db = Database::new(S);
84    /// assert_eq!(db.revision().as_u64(), 0);
85    /// db.set(1, 1);
86    /// assert_eq!(db.revision().as_u64(), 1);
87    /// ```
88    #[must_use]
89    #[inline]
90    pub const fn as_u64(self) -> u64 {
91        self.0
92    }
93}
94
95impl fmt::Display for Revision {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "r{}", self.0)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_start_is_zero() {
107        assert_eq!(Revision::START.as_u64(), 0);
108    }
109
110    #[test]
111    fn test_next_advances_by_one() {
112        assert_eq!(Revision::START.next().as_u64(), 1);
113        assert_eq!(Revision::START.next().next().as_u64(), 2);
114    }
115
116    #[test]
117    fn test_ordering_reflects_age() {
118        assert!(Revision::START.next() > Revision::START);
119        assert!(Revision::START < Revision::START.next());
120    }
121
122    #[test]
123    fn test_next_saturates_at_max() {
124        let max = Revision(u64::MAX);
125        assert_eq!(max.next(), max);
126    }
127
128    #[test]
129    fn test_display_is_prefixed() {
130        use alloc::string::ToString;
131        assert_eq!(Revision(7).to_string(), "r7");
132    }
133}