surrealdb_sql/
object.rs

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