surrealdb_core/cf/
mutations.rs

1use crate::fflags::FFLAGS;
2use crate::sql::array::Array;
3use crate::sql::object::Object;
4use crate::sql::statements::DefineTableStatement;
5use crate::sql::thing::Thing;
6use crate::sql::value::Value;
7use crate::sql::Operation;
8use crate::vs::VersionStamp;
9use revision::revisioned;
10use serde::{Deserialize, Serialize};
11use std::collections::{BTreeMap, HashMap};
12use std::fmt::{self, Display, Formatter};
13
14// Mutation is a single mutation to a table.
15#[revisioned(revision = 2)]
16#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
17#[non_exhaustive]
18pub enum TableMutation {
19	// Although the Value is supposed to contain a field "id" of Thing,
20	// we do include it in the first field for convenience.
21	Set(Thing, Value),
22	Del(Thing),
23	Def(DefineTableStatement),
24	#[revision(start = 2)]
25	/// Includes the ID, current value (after change), changes that can be applied to get the original
26	/// value
27	/// Example, ("mytb:tobie", {{"note": "surreal"}}, [{"op": "add", "path": "/note", "value": "surreal"}], false)
28	/// Means that we have already applied the add "/note" operation to achieve the recorded result
29	SetWithDiff(Thing, Value, Vec<Operation>),
30	#[revision(start = 2)]
31	/// Delete a record where the ID is stored, and the now-deleted value
32	DelWithOriginal(Thing, Value),
33}
34
35impl From<DefineTableStatement> for Value {
36	#[inline]
37	fn from(v: DefineTableStatement) -> Self {
38		let mut h = HashMap::<&str, Value>::new();
39		if let Some(id) = v.id {
40			h.insert("id", id.into());
41		}
42		h.insert("name", v.name.0.into());
43		Value::Object(Object::from(h))
44	}
45}
46
47#[revisioned(revision = 1)]
48#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
49#[non_exhaustive]
50pub struct TableMutations(pub String, pub Vec<TableMutation>);
51
52impl TableMutations {
53	pub fn new(tb: String) -> Self {
54		Self(tb, Vec::new())
55	}
56}
57
58#[revisioned(revision = 1)]
59#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
60#[non_exhaustive]
61pub struct DatabaseMutation(pub Vec<TableMutations>);
62
63impl DatabaseMutation {
64	pub fn new() -> Self {
65		Self(Vec::new())
66	}
67}
68
69impl Default for DatabaseMutation {
70	fn default() -> Self {
71		Self::new()
72	}
73}
74
75// Change is a set of mutations made to a table at the specific timestamp.
76#[revisioned(revision = 1)]
77#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
78#[non_exhaustive]
79pub struct ChangeSet(pub VersionStamp, pub DatabaseMutation);
80
81impl TableMutation {
82	/// Convert a stored change feed table mutation (record change) into a
83	/// Value that can be used in the storage of change feeds and their transmission to consumers
84	pub fn into_value(self) -> Value {
85		let mut h = BTreeMap::<String, Value>::new();
86		let h = match self {
87			TableMutation::Set(_thing, v) => {
88				if FFLAGS.change_feed_live_queries.enabled() {
89					h.insert("create".to_string(), v);
90				} else {
91					h.insert("update".to_string(), v);
92				}
93				h
94			}
95			TableMutation::SetWithDiff(_thing, current, operations) => {
96				h.insert("current".to_string(), current);
97				h.insert(
98					"update".to_string(),
99					Value::Array(Array(
100						operations
101							.clone()
102							.into_iter()
103							.map(|x| Value::Object(Object::from(x)))
104							.collect(),
105					)),
106				);
107				h
108			}
109			TableMutation::Del(t) => {
110				h.insert(
111					"delete".to_string(),
112					Value::Object(Object::from(map! {
113						"id".to_string() => Value::Thing(t)
114					})),
115				);
116				h
117			}
118			TableMutation::Def(t) => {
119				h.insert("define_table".to_string(), Value::from(t));
120				h
121			}
122			TableMutation::DelWithOriginal(id, _val) => {
123				h.insert(
124					"delete".to_string(),
125					Value::Object(Object::from(map! {
126					"id".to_string() => Value::Thing(id),
127					})),
128				);
129				h
130			}
131		};
132		let o = crate::sql::object::Object::from(h);
133		Value::Object(o)
134	}
135}
136
137impl DatabaseMutation {
138	pub fn into_value(self) -> Value {
139		let mut changes = Vec::<Value>::new();
140		for tbs in self.0 {
141			for tb in tbs.1 {
142				changes.push(tb.into_value());
143			}
144		}
145		Value::Array(Array::from(changes))
146	}
147}
148
149impl ChangeSet {
150	pub fn into_value(self) -> Value {
151		let mut m = BTreeMap::<String, Value>::new();
152		m.insert("versionstamp".to_string(), Value::from(self.0.into_u128()));
153		m.insert("changes".to_string(), self.1.into_value());
154		let so: Object = m.into();
155		Value::Object(so)
156	}
157}
158
159impl Display for TableMutation {
160	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
161		match self {
162			TableMutation::Set(id, v) => write!(f, "SET {} {}", id, v),
163			TableMutation::SetWithDiff(id, _previous, v) => write!(f, "SET {} {:?}", id, v),
164			TableMutation::Del(id) => write!(f, "DEL {}", id),
165			TableMutation::DelWithOriginal(id, _) => write!(f, "DEL {}", id),
166			TableMutation::Def(t) => write!(f, "{}", t),
167		}
168	}
169}
170
171impl Display for TableMutations {
172	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
173		let tb = &self.0;
174		let muts = &self.1;
175		write!(f, "{}", tb)?;
176		muts.iter().try_for_each(|v| write!(f, "{}", v))
177	}
178}
179
180impl Display for DatabaseMutation {
181	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
182		let x = &self.0;
183
184		x.iter().try_for_each(|v| write!(f, "{}", v))
185	}
186}
187
188impl Display for ChangeSet {
189	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
190		let x = &self.1;
191
192		write!(f, "{}", x)
193	}
194}
195
196// WriteMutationSet is a set of mutations to be to a table at the specific timestamp.
197#[revisioned(revision = 1)]
198#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
199#[non_exhaustive]
200pub struct WriteMutationSet(pub Vec<TableMutations>);
201
202impl WriteMutationSet {
203	pub fn new() -> Self {
204		Self(Vec::new())
205	}
206}
207
208impl Default for WriteMutationSet {
209	fn default() -> Self {
210		Self::new()
211	}
212}
213
214#[cfg(test)]
215mod tests {
216	#[test]
217	fn serialization() {
218		use super::*;
219		use std::collections::HashMap;
220		let cs = ChangeSet(
221			VersionStamp::from_u64(1),
222			DatabaseMutation(vec![TableMutations(
223				"mytb".to_string(),
224				vec![
225					TableMutation::Set(
226						Thing::from(("mytb".to_string(), "tobie".to_string())),
227						Value::Object(Object::from(HashMap::from([
228							(
229								"id",
230								Value::from(Thing::from(("mytb".to_string(), "tobie".to_string()))),
231							),
232							("note", Value::from("surreal")),
233						]))),
234					),
235					TableMutation::Del(Thing::from(("mytb".to_string(), "tobie".to_string()))),
236					TableMutation::Def(DefineTableStatement {
237						name: "mytb".into(),
238						..DefineTableStatement::default()
239					}),
240				],
241			)]),
242		);
243		let v = cs.into_value().into_json();
244		let s = serde_json::to_string(&v).unwrap();
245		assert_eq!(
246			s,
247			r#"{"changes":[{"update":{"id":"mytb:tobie","note":"surreal"}},{"delete":{"id":"mytb:tobie"}},{"define_table":{"name":"mytb"}}],"versionstamp":65536}"#
248		);
249	}
250
251	#[test]
252	fn serialization_rev2() {
253		use super::*;
254		use std::collections::HashMap;
255		let cs = ChangeSet(
256			VersionStamp::from_u64(1),
257			DatabaseMutation(vec![TableMutations(
258				"mytb".to_string(),
259				vec![
260					TableMutation::SetWithDiff(
261						Thing::from(("mytb".to_string(), "tobie".to_string())),
262						Value::Object(Object::from(HashMap::from([
263							(
264								"id",
265								Value::from(Thing::from(("mytb".to_string(), "tobie".to_string()))),
266							),
267							("note", Value::from("surreal")),
268						]))),
269						vec![Operation::Add {
270							path: "/note".into(),
271							value: Value::from("surreal"),
272						}],
273					),
274					TableMutation::SetWithDiff(
275						Thing::from(("mytb".to_string(), "tobie".to_string())),
276						Value::Object(Object::from(HashMap::from([
277							(
278								"id",
279								Value::from(Thing::from((
280									"mytb".to_string(),
281									"tobie2".to_string(),
282								))),
283							),
284							("note", Value::from("surreal")),
285						]))),
286						vec![Operation::Remove {
287							path: "/temp".into(),
288						}],
289					),
290					TableMutation::Del(Thing::from(("mytb".to_string(), "tobie".to_string()))),
291					TableMutation::DelWithOriginal(
292						Thing::from(("mytb".to_string(), "tobie".to_string())),
293						Value::Object(Object::from(map! {
294								"id" => Value::from(Thing::from(("mytb".to_string(), "tobie".to_string()))),
295								"note" => Value::from("surreal"),
296						})),
297					),
298					TableMutation::Def(DefineTableStatement {
299						name: "mytb".into(),
300						..DefineTableStatement::default()
301					}),
302				],
303			)]),
304		);
305		let v = cs.into_value().into_json();
306		let s = serde_json::to_string(&v).unwrap();
307		assert_eq!(
308			s,
309			r#"{"changes":[{"current":{"id":"mytb:tobie","note":"surreal"},"update":[{"op":"add","path":"/`/note`","value":"surreal"}]},{"current":{"id":"mytb:tobie2","note":"surreal"},"update":[{"op":"remove","path":"/`/temp`"}]},{"delete":{"id":"mytb:tobie"}},{"delete":{"id":"mytb:tobie"}},{"define_table":{"name":"mytb"}}],"versionstamp":65536}"#
310		);
311	}
312}