1use crate::changes::{CaughtChange, CaughtNode};
22use crate::value::{OwnedValue, RelId};
23
24pub type WireRow = Vec<OwnedValue>;
26
27#[cfg_attr(
34 any(feature = "testkit", feature = "serde"),
35 derive(serde::Serialize, serde::Deserialize)
36)]
37#[derive(Clone, Debug)]
38pub struct WireNode {
39 pub row: WireRow,
40 pub rels: Vec<(RelId, Vec<WireNode>)>,
41}
42
43#[cfg_attr(
46 any(feature = "testkit", feature = "serde"),
47 derive(serde::Serialize, serde::Deserialize)
48)]
49#[derive(Clone, Debug)]
50pub struct PathSeg {
51 pub rel: RelId,
52 pub parent_row: WireRow,
53}
54
55#[cfg_attr(
57 any(feature = "testkit", feature = "serde"),
58 derive(serde::Serialize, serde::Deserialize)
59)]
60#[derive(Clone, Debug)]
61pub enum FlatOp {
62 Add(WireNode),
64 Remove { row: WireRow },
68 Edit { old: WireRow, new: WireRow },
71}
72
73#[cfg_attr(
76 any(feature = "testkit", feature = "serde"),
77 derive(serde::Serialize, serde::Deserialize)
78)]
79#[derive(Clone, Debug)]
80pub struct FlatChange {
81 pub path: Vec<PathSeg>,
82 pub op: FlatOp,
83}
84
85pub fn flatten(change: &CaughtChange) -> FlatChange {
88 let mut path = Vec::new();
89 let mut cur = change;
90 loop {
91 match cur {
92 CaughtChange::Child { row, rel, change } => {
93 path.push(PathSeg {
94 rel: *rel,
95 parent_row: row.to_value_vec(),
96 });
97 cur = &**change;
98 }
99 CaughtChange::Add(node) => {
100 return FlatChange {
101 path,
102 op: FlatOp::Add(wire_node(node)),
103 };
104 }
105 CaughtChange::Remove(node) => {
106 return FlatChange {
107 path,
108 op: FlatOp::Remove {
109 row: node.row.to_value_vec(),
110 },
111 };
112 }
113 CaughtChange::Edit { old, row } => {
114 return FlatChange {
115 path,
116 op: FlatOp::Edit {
117 old: old.to_value_vec(),
118 new: row.to_value_vec(),
119 },
120 };
121 }
122 }
123 }
124}
125
126pub fn flatten_all(changes: &[CaughtChange]) -> Vec<FlatChange> {
130 changes.iter().map(flatten).collect()
131}
132
133fn wire_node(node: &CaughtNode) -> WireNode {
137 WireNode {
138 row: node.row.to_value_vec(),
139 rels: node
140 .relationships
141 .iter()
142 .map(|(slot, children)| (*slot, children.iter().map(wire_node).collect()))
143 .collect(),
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::changes::{CaughtChange, CaughtNode};
151 use crate::value::{owned_row, OwnedValue as V};
152
153 fn cnode(row: Vec<V>, rels: Vec<(RelId, Vec<CaughtNode>)>) -> CaughtNode {
154 CaughtNode {
155 row: owned_row(row),
156 relationships: rels.into_iter().collect(),
157 }
158 }
159
160 fn as_int(v: &V) -> i64 {
161 match v {
162 V::Int(i) => *i,
163 other => panic!("expected Int, got {other:?}"),
164 }
165 }
166
167 #[test]
168 fn flatten_peels_child_chain_into_path() {
169 let change = CaughtChange::Child {
172 row: owned_row(vec![V::Int(1)]),
173 rel: RelId(0),
174 change: Box::new(CaughtChange::Child {
175 row: owned_row(vec![V::Int(2)]),
176 rel: RelId(1),
177 change: Box::new(CaughtChange::Edit {
178 old: owned_row(vec![V::Int(3), V::Int(10)]),
179 row: owned_row(vec![V::Int(3), V::Int(20)]),
180 }),
181 }),
182 };
183 let flat = flatten(&change);
184 assert_eq!(flat.path.len(), 2);
185 assert_eq!(flat.path[0].rel, RelId(0));
186 assert_eq!(as_int(&flat.path[0].parent_row[0]), 1);
187 assert_eq!(flat.path[1].rel, RelId(1));
188 assert_eq!(as_int(&flat.path[1].parent_row[0]), 2);
189 match &flat.op {
190 FlatOp::Edit { old, new } => {
191 assert_eq!(as_int(&old[1]), 10);
192 assert_eq!(as_int(&new[1]), 20);
193 }
194 other => panic!("expected Edit, got {other:?}"),
195 }
196 }
197
198 #[test]
199 fn flatten_add_carries_slot_keyed_subtree() {
200 let node = cnode(
202 vec![V::Int(1)],
203 vec![(RelId(0), vec![cnode(vec![V::Int(100)], vec![])])],
204 );
205 let flat = flatten(&CaughtChange::Add(node));
206 assert!(flat.path.is_empty());
207 match &flat.op {
208 FlatOp::Add(wn) => {
209 assert_eq!(as_int(&wn.row[0]), 1);
210 assert_eq!(wn.rels.len(), 1);
211 assert_eq!(wn.rels[0].0, RelId(0));
212 assert_eq!(wn.rels[0].1.len(), 1);
213 assert_eq!(as_int(&wn.rels[0].1[0].row[0]), 100);
214 }
215 other => panic!("expected Add, got {other:?}"),
216 }
217 }
218
219 #[test]
220 fn flatten_remove_is_row_only() {
221 let node = cnode(
223 vec![V::Int(7)],
224 vec![(RelId(0), vec![cnode(vec![V::Int(1)], vec![])])],
225 );
226 let flat = flatten(&CaughtChange::Remove(node));
227 match &flat.op {
228 FlatOp::Remove { row } => assert_eq!(as_int(&row[0]), 7),
229 other => panic!("expected Remove, got {other:?}"),
230 }
231 }
232
233 #[cfg(any(feature = "testkit", feature = "serde"))]
234 #[test]
235 fn flat_change_serde_round_trips() {
236 let change = CaughtChange::Child {
239 row: owned_row(vec![V::Int(1), V::str("a")]),
240 rel: RelId(0),
241 change: Box::new(CaughtChange::Add(cnode(
242 vec![
243 V::Int(2),
244 V::Float(1.5),
245 V::Null,
246 V::Bool(true),
247 V::str("hi"),
248 V::Json(std::sync::Arc::from("{\"k\":1}")),
249 ],
250 vec![(RelId(0), vec![cnode(vec![V::Int(3)], vec![])])],
251 ))),
252 };
253 let flat = flatten(&change);
254 let json = serde_json::to_value(&flat).expect("serialize");
255 let back: FlatChange = serde_json::from_value(json.clone()).expect("deserialize");
256 let json2 = serde_json::to_value(&back).expect("re-serialize");
257 assert_eq!(json, json2, "flat change must round-trip through serde");
258 }
259}