surrealdb/sql/
object.rs

1use crate::ctx::Context;
2use crate::dbs::{Options, Transaction};
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::sql::{
6	escape::escape_key,
7	fmt::{is_pretty, pretty_indent, Fmt, Pretty},
8	Operation, Thing, Value,
9};
10use revision::revisioned;
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeMap;
13use std::collections::HashMap;
14use std::fmt::{self, Display, Formatter, Write};
15use std::ops::Deref;
16use std::ops::DerefMut;
17
18pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Object";
19
20/// Invariant: Keys never contain NUL bytes.
21#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
22#[serde(rename = "$surrealdb::private::sql::Object")]
23#[revisioned(revision = 1)]
24pub struct Object(#[serde(with = "no_nul_bytes_in_keys")] pub BTreeMap<String, Value>);
25
26impl From<BTreeMap<String, Value>> for Object {
27	fn from(v: BTreeMap<String, Value>) -> Self {
28		Self(v)
29	}
30}
31
32impl From<HashMap<&str, Value>> for Object {
33	fn from(v: HashMap<&str, Value>) -> Self {
34		Self(v.into_iter().map(|(key, val)| (key.to_string(), val)).collect())
35	}
36}
37
38impl From<HashMap<String, Value>> for Object {
39	fn from(v: HashMap<String, Value>) -> Self {
40		Self(v.into_iter().collect())
41	}
42}
43
44impl From<Option<Self>> for Object {
45	fn from(v: Option<Self>) -> Self {
46		v.unwrap_or_default()
47	}
48}
49
50impl From<Operation> for Object {
51	fn from(v: Operation) -> Self {
52		Self(match v {
53			Operation::Add {
54				path,
55				value,
56			} => map! {
57				String::from("op") => Value::from("add"),
58				String::from("path") => path.to_path().into(),
59				String::from("value") => value
60			},
61			Operation::Remove {
62				path,
63			} => map! {
64				String::from("op") => Value::from("remove"),
65				String::from("path") => path.to_path().into()
66			},
67			Operation::Replace {
68				path,
69				value,
70			} => map! {
71				String::from("op") => Value::from("replace"),
72				String::from("path") => path.to_path().into(),
73				String::from("value") => value
74			},
75			Operation::Change {
76				path,
77				value,
78			} => map! {
79				String::from("op") => Value::from("change"),
80				String::from("path") => path.to_path().into(),
81				String::from("value") => value
82			},
83			Operation::Copy {
84				path,
85				from,
86			} => map! {
87				String::from("op") => Value::from("copy"),
88				String::from("path") => path.to_path().into(),
89				String::from("from") => from.to_path().into()
90			},
91			Operation::Move {
92				path,
93				from,
94			} => map! {
95				String::from("op") => Value::from("move"),
96				String::from("path") => path.to_path().into(),
97				String::from("from") => from.to_path().into()
98			},
99			Operation::Test {
100				path,
101				value,
102			} => map! {
103				String::from("op") => Value::from("test"),
104				String::from("path") => path.to_path().into(),
105				String::from("value") => value
106			},
107		})
108	}
109}
110
111impl Deref for Object {
112	type Target = BTreeMap<String, Value>;
113	fn deref(&self) -> &Self::Target {
114		&self.0
115	}
116}
117
118impl DerefMut for Object {
119	fn deref_mut(&mut self) -> &mut Self::Target {
120		&mut self.0
121	}
122}
123
124impl IntoIterator for Object {
125	type Item = (String, Value);
126	type IntoIter = std::collections::btree_map::IntoIter<String, Value>;
127	fn into_iter(self) -> Self::IntoIter {
128		self.0.into_iter()
129	}
130}
131
132impl Object {
133	/// Fetch the record id if there is one
134	pub fn rid(&self) -> Option<Thing> {
135		match self.get("id") {
136			Some(Value::Thing(v)) => Some(v.clone()),
137			_ => None,
138		}
139	}
140	/// Convert this object to a diff-match-patch operation
141	pub fn to_operation(&self) -> Result<Operation, Error> {
142		match self.get("op") {
143			Some(op_val) => match self.get("path") {
144				Some(path_val) => {
145					let path = path_val.jsonpath();
146
147					let from =
148						self.get("from").map(|value| value.jsonpath()).ok_or(Error::InvalidPatch {
149							message: String::from("'from' key missing"),
150						});
151
152					let value = self.get("value").cloned().ok_or(Error::InvalidPatch {
153						message: String::from("'value' key missing"),
154					});
155
156					match op_val.clone().as_string().as_str() {
157						// Add operation
158						"add" => Ok(Operation::Add {
159							path,
160							value: value?,
161						}),
162						// Remove operation
163						"remove" => Ok(Operation::Remove {
164							path,
165						}),
166						// Replace operation
167						"replace" => Ok(Operation::Replace {
168							path,
169							value: value?,
170						}),
171						// Change operation
172						"change" => Ok(Operation::Change {
173							path,
174							value: value?,
175						}),
176						// Copy operation
177						"copy" => Ok(Operation::Copy {
178							path,
179							from: from?,
180						}),
181						// Move operation
182						"move" => Ok(Operation::Move {
183							path,
184							from: from?,
185						}),
186						// Test operation
187						"test" => Ok(Operation::Test {
188							path,
189							value: value?,
190						}),
191						unknown_op => Err(Error::InvalidPatch {
192							message: format!("unknown op '{unknown_op}'"),
193						}),
194					}
195				}
196				_ => Err(Error::InvalidPatch {
197					message: String::from("'path' key missing"),
198				}),
199			},
200			_ => Err(Error::InvalidPatch {
201				message: String::from("'op' key missing"),
202			}),
203		}
204	}
205}
206
207impl Object {
208	/// Process this type returning a computed simple Value
209	pub(crate) async fn compute(
210		&self,
211		ctx: &Context<'_>,
212		opt: &Options,
213		txn: &Transaction,
214		doc: Option<&CursorDoc<'_>>,
215	) -> Result<Value, Error> {
216		let mut x = BTreeMap::new();
217		for (k, v) in self.iter() {
218			match v.compute(ctx, opt, txn, doc).await {
219				Ok(v) => x.insert(k.clone(), v),
220				Err(e) => return Err(e),
221			};
222		}
223		Ok(Value::Object(Object(x)))
224	}
225}
226
227impl Display for Object {
228	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
229		let mut f = Pretty::from(f);
230		if is_pretty() {
231			f.write_char('{')?;
232		} else {
233			f.write_str("{ ")?;
234		}
235		if !self.is_empty() {
236			let indent = pretty_indent();
237			write!(
238				f,
239				"{}",
240				Fmt::pretty_comma_separated(
241					self.0.iter().map(|args| Fmt::new(args, |(k, v), f| write!(
242						f,
243						"{}: {}",
244						escape_key(k),
245						v
246					))),
247				)
248			)?;
249			drop(indent);
250		}
251		if is_pretty() {
252			f.write_char('}')
253		} else {
254			f.write_str(" }")
255		}
256	}
257}
258
259mod no_nul_bytes_in_keys {
260	use serde::{
261		de::{self, Visitor},
262		ser::SerializeMap,
263		Deserializer, Serializer,
264	};
265	use std::{collections::BTreeMap, fmt};
266
267	use crate::sql::Value;
268
269	pub(crate) fn serialize<S>(
270		m: &BTreeMap<String, Value>,
271		serializer: S,
272	) -> Result<S::Ok, S::Error>
273	where
274		S: Serializer,
275	{
276		let mut s = serializer.serialize_map(Some(m.len()))?;
277		for (k, v) in m {
278			debug_assert!(!k.contains('\0'));
279			s.serialize_entry(k, v)?;
280		}
281		s.end()
282	}
283
284	pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<BTreeMap<String, Value>, D::Error>
285	where
286		D: Deserializer<'de>,
287	{
288		struct NoNulBytesInKeysVisitor;
289
290		impl<'de> Visitor<'de> for NoNulBytesInKeysVisitor {
291			type Value = BTreeMap<String, Value>;
292
293			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
294				formatter.write_str("a map without any NUL bytes in its keys")
295			}
296
297			fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
298			where
299				A: de::MapAccess<'de>,
300			{
301				let mut ret = BTreeMap::new();
302				while let Some((k, v)) = map.next_entry()? {
303					ret.insert(k, v);
304				}
305				Ok(ret)
306			}
307		}
308
309		deserializer.deserialize_map(NoNulBytesInKeysVisitor)
310	}
311}