Skip to main content

reifydb_core/interface/
change.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::mem;
5
6use arrow_array::RecordBatch;
7use reifydb_value::{
8	Result,
9	value::{datetime::DateTime, diff_type::DiffType},
10};
11use serde::{Deserialize, Serialize};
12use smallvec::SmallVec;
13
14use crate::{
15	common::ChangeVersion,
16	interface::{
17		catalog::{flow::OperatorId, object::ObjectId},
18		consolidate::coalesce_diffs,
19	},
20};
21
22pub type Diffs = SmallVec<[Diff; 4]>;
23
24pub type StagedBatch = (DiffType, RecordBatch);
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub enum ChangeOrigin {
28	Object(ObjectId),
29	Flow(OperatorId),
30}
31
32#[derive(Debug, Clone, PartialEq)]
33pub enum Diff {
34	Insert {
35		post: RecordBatch,
36		origin: Option<ChangeOrigin>,
37	},
38	Update {
39		pre: RecordBatch,
40		post: RecordBatch,
41		origin: Option<ChangeOrigin>,
42	},
43	Remove {
44		pre: RecordBatch,
45		origin: Option<ChangeOrigin>,
46	},
47}
48
49impl Diff {
50	pub fn insert(post: RecordBatch) -> Self {
51		Self::Insert {
52			post,
53			origin: None,
54		}
55	}
56
57	pub fn update(pre: RecordBatch, post: RecordBatch) -> Self {
58		Self::update_with_origin(pre, post, None)
59	}
60
61	pub fn update_with_origin(pre: RecordBatch, post: RecordBatch, origin: Option<ChangeOrigin>) -> Self {
62		assert!(
63			pre.num_rows() == post.num_rows(),
64			"an update change needs as many pre rows as post rows, got {} pre and {} post",
65			pre.num_rows(),
66			post.num_rows()
67		);
68		Self::Update {
69			pre,
70			post,
71			origin,
72		}
73	}
74
75	pub fn remove(pre: RecordBatch) -> Self {
76		Self::Remove {
77			pre,
78			origin: None,
79		}
80	}
81
82	pub fn pre(&self) -> Option<&RecordBatch> {
83		match self {
84			Diff::Insert {
85				..
86			} => None,
87			Diff::Update {
88				pre,
89				..
90			} => Some(pre),
91			Diff::Remove {
92				pre,
93				..
94			} => Some(pre),
95		}
96	}
97
98	pub fn post(&self) -> Option<&RecordBatch> {
99		match self {
100			Diff::Insert {
101				post,
102				..
103			} => Some(post),
104			Diff::Update {
105				post,
106				..
107			} => Some(post),
108			Diff::Remove {
109				..
110			} => None,
111		}
112	}
113
114	pub fn batches_mut(&mut self) -> impl Iterator<Item = &mut RecordBatch> {
115		let pair: [Option<&mut RecordBatch>; 2] = match self {
116			Diff::Insert {
117				post,
118				..
119			} => [Some(post), None],
120			Diff::Update {
121				pre,
122				post,
123				..
124			} => [Some(pre), Some(post)],
125			Diff::Remove {
126				pre,
127				..
128			} => [Some(pre), None],
129		};
130		pair.into_iter().flatten()
131	}
132
133	pub fn kind(&self) -> DiffType {
134		match self {
135			Diff::Insert {
136				..
137			} => DiffType::Insert,
138			Diff::Update {
139				..
140			} => DiffType::Update,
141			Diff::Remove {
142				..
143			} => DiffType::Remove,
144		}
145	}
146
147	pub fn row_count(&self) -> usize {
148		match self {
149			Diff::Insert {
150				post,
151				..
152			} => post.num_rows(),
153			Diff::Update {
154				post,
155				..
156			} => post.num_rows(),
157			Diff::Remove {
158				pre,
159				..
160			} => pre.num_rows(),
161		}
162	}
163
164	pub fn origin(&self) -> Option<&ChangeOrigin> {
165		match self {
166			Diff::Insert {
167				origin,
168				..
169			} => origin.as_ref(),
170			Diff::Update {
171				origin,
172				..
173			} => origin.as_ref(),
174			Diff::Remove {
175				origin,
176				..
177			} => origin.as_ref(),
178		}
179	}
180
181	pub fn set_origin(&mut self, new_origin: Option<ChangeOrigin>) {
182		match self {
183			Diff::Insert {
184				origin,
185				..
186			} => *origin = new_origin,
187			Diff::Update {
188				origin,
189				..
190			} => *origin = new_origin,
191			Diff::Remove {
192				origin,
193				..
194			} => *origin = new_origin,
195		}
196	}
197}
198
199#[derive(Debug, Clone)]
200pub struct Change {
201	pub origin: ChangeOrigin,
202
203	pub diffs: Diffs,
204
205	pub version: ChangeVersion,
206
207	pub changed_at: DateTime,
208}
209
210impl Change {
211	pub fn from_object(
212		object: ObjectId,
213		version: ChangeVersion,
214		diffs: impl Into<Diffs>,
215		changed_at: DateTime,
216	) -> Self {
217		Self {
218			origin: ChangeOrigin::Object(object),
219			diffs: diffs.into(),
220			version,
221			changed_at,
222		}
223	}
224
225	pub fn from_flow(
226		from: OperatorId,
227		version: ChangeVersion,
228		diffs: impl Into<Diffs>,
229		changed_at: DateTime,
230	) -> Self {
231		Self {
232			origin: ChangeOrigin::Flow(from),
233			diffs: diffs.into(),
234			version,
235			changed_at,
236		}
237	}
238
239	pub fn from_operator(
240		operator: OperatorId,
241		version: ChangeVersion,
242		changed_at: DateTime,
243		result: Change,
244	) -> Self {
245		Self::from_flow(operator, version, result.diffs, changed_at.max(result.changed_at))
246	}
247
248	pub fn row_count(&self) -> usize {
249		self.diffs.iter().map(Diff::row_count).sum()
250	}
251
252	pub fn merge(changes: Vec<Change>) -> Result<Change> {
253		let mut iter = changes.into_iter();
254		let mut merged = iter.next().expect("Change::merge requires at least one Change");
255		for mut ch in iter {
256			if ch.changed_at > merged.changed_at {
257				merged.changed_at = ch.changed_at;
258			}
259			if ch.version.commit > merged.version.commit {
260				merged.version.commit = ch.version.commit;
261			}
262			if ch.version.source > merged.version.source {
263				merged.version.source = ch.version.source;
264			}
265			if ch.origin != merged.origin {
266				for diff in ch.diffs.iter_mut() {
267					if diff.origin().is_none() {
268						diff.set_origin(Some(ch.origin.clone()));
269					}
270				}
271			}
272			merged.diffs.extend(ch.diffs);
273		}
274		merged.coalesce()?;
275		Ok(merged)
276	}
277
278	pub fn coalesce(&mut self) -> Result<()> {
279		if self.diffs.len() <= 1 {
280			return Ok(());
281		}
282		let original = mem::take(&mut self.diffs);
283		self.diffs = SmallVec::from_vec(coalesce_diffs(original.into_vec())?);
284		Ok(())
285	}
286}