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