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}