Skip to main content

dataflow_rs/base/
change.rs

1//! The wire shape of one recorded write.
2
3use datavalue::OwnedDataValue;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::sync::Arc;
7
8/// One write or removal, written as v3 wrote one.
9///
10/// A path that was absent before the write records `old_value` as `null`.
11#[derive(Clone, Serialize, Deserialize)]
12#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
13#[non_exhaustive]
14pub struct Change {
15    /// The dotted path written.
16    pub path: Arc<str>,
17    /// What the path held before; `null` when it was absent.
18    #[serde(deserialize_with = "super::exact")]
19    #[cfg_attr(feature = "ts", ts(type = "JsonValue"))]
20    pub old_value: OwnedDataValue,
21    /// The value written; `null` for a removal (read
22    /// [`removed`](Self::removed) to tell a removal from a write of `null`).
23    #[serde(deserialize_with = "super::exact")]
24    #[cfg_attr(feature = "ts", ts(type = "JsonValue"))]
25    pub new_value: OwnedDataValue,
26    /// The key at `path` was removed rather than written. Absent when
27    /// `false`, so a write's JSON is v3's.
28    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
29    pub removed: bool,
30    /// The write replaced the root at `path` instead of merging into it: a
31    /// host function's write at a root replaces it, and `map`'s root
32    /// mapping, which records only the keys it wrote, merges. A trace's
33    /// changes carry it, so a replay knows which; an audit entry's are
34    /// always `false`, so the audit trail's JSON is v3's. Absent when
35    /// `false`.
36    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
37    pub replaced: bool,
38    /// The change is the trace's alone: a write the call made that v3's
39    /// audit entry has no row for, which a replay needs, such as the array a
40    /// `map` append creates. The recorder leaves it out of the audit entry.
41    /// Never serialized, and neither compared nor shown.
42    #[serde(skip)]
43    pub(crate) trace_only: bool,
44}
45
46impl PartialEq for Change {
47    /// The wire fields; not whether the change is the trace's alone. The
48    /// destructuring names every field, so a field added later does not
49    /// compile until it is compared or deliberately left out.
50    fn eq(&self, other: &Self) -> bool {
51        let Self {
52            path,
53            old_value,
54            new_value,
55            removed,
56            replaced,
57            trace_only: _,
58        } = self;
59        *path == other.path
60            && *old_value == other.old_value
61            && *new_value == other.new_value
62            && *removed == other.removed
63            && *replaced == other.replaced
64    }
65}
66
67impl fmt::Debug for Change {
68    /// The wire fields, as a derive would show them; every field named, as
69    /// in `eq`.
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        let Self {
72            path,
73            old_value,
74            new_value,
75            removed,
76            replaced,
77            trace_only: _,
78        } = self;
79        f.debug_struct("Change")
80            .field("path", path)
81            .field("old_value", old_value)
82            .field("new_value", new_value)
83            .field("removed", removed)
84            .field("replaced", replaced)
85            .finish()
86    }
87}
88
89impl Change {
90    /// A write of `new_value` over `old_value` at `path`.
91    pub fn write(
92        path: impl Into<Arc<str>>,
93        old_value: OwnedDataValue,
94        new_value: OwnedDataValue,
95    ) -> Self {
96        Self {
97            path: path.into(),
98            old_value,
99            new_value,
100            removed: false,
101            replaced: false,
102            trace_only: false,
103        }
104    }
105
106    /// An assignment of `new_value` over `old_value` at `path`, as a host
107    /// function's `Ctx::set` and the engine's own writes make one: at a root
108    /// it replaces the root, and says so ([`replaced`](Self::replaced)). A
109    /// `map` root mapping merges instead, and records a plain
110    /// [`write`](Self::write).
111    pub(crate) fn assign(
112        path: &crate::expr::Path,
113        old_value: OwnedDataValue,
114        new_value: OwnedDataValue,
115    ) -> Self {
116        Self {
117            replaced: path.is_root(),
118            ..Self::write(Arc::clone(path.dotted()), old_value, new_value)
119        }
120    }
121
122    /// A removal of the key at `path`, which held `old_value`.
123    pub fn removal(path: impl Into<Arc<str>>, old_value: OwnedDataValue) -> Self {
124        Self {
125            path: path.into(),
126            old_value,
127            new_value: OwnedDataValue::Null,
128            removed: true,
129            replaced: false,
130            trace_only: false,
131        }
132    }
133
134    /// A write only the trace keeps ([`trace_only`](Self::trace_only)).
135    pub(crate) fn for_trace(
136        path: impl Into<Arc<str>>,
137        old_value: OwnedDataValue,
138        new_value: OwnedDataValue,
139    ) -> Self {
140        Self {
141            trace_only: true,
142            ..Self::write(path, old_value, new_value)
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::expr::Path;
151
152    fn path(dotted: &str) -> Path {
153        dotted.parse().unwrap()
154    }
155
156    #[test]
157    fn each_kind_of_record_sets_its_own_flags() {
158        let (old, new) = (OwnedDataValue::Null, OwnedDataValue::from(1));
159        let flags = |c: &Change| (c.removed, c.replaced, c.trace_only);
160        // An assignment replaces a root, and says so; below a root it is a
161        // plain write.
162        assert_eq!(
163            flags(&Change::assign(&path("data"), old.clone(), new.clone())),
164            (false, true, false)
165        );
166        assert_eq!(
167            flags(&Change::assign(&path("data.a"), old.clone(), new.clone())),
168            (false, false, false)
169        );
170        // `map`'s root mapping merges: a plain write, never `replaced`.
171        assert_eq!(
172            flags(&Change::write("data", old.clone(), new.clone())),
173            (false, false, false)
174        );
175        assert_eq!(
176            flags(&Change::removal("data.a", new.clone())),
177            (true, false, false)
178        );
179        assert_eq!(
180            flags(&Change::for_trace("data.a", old, new)),
181            (false, false, true)
182        );
183    }
184}