Skip to main content

surrealdb_expr/val/
object.rs

1use std::collections::{BTreeMap, HashMap};
2use std::ops::{Deref, DerefMut};
3
4use anyhow::Result;
5use common::fmt::EscapeObjectKey;
6use http::{HeaderMap, HeaderName, HeaderValue};
7use revision::revisioned;
8use storekey::{BorrowDecode, Encode};
9use surrealdb_collections::{Entry as VecMapEntry, VecMap, VecMapIntoIter};
10use surrealdb_types::{SqlFormat, ToSql, write_sql};
11
12use crate::expr::literal::ObjectEntry;
13use crate::val::{IndexFormat, RecordId, Strand, Value};
14
15/// Invariant: Keys never contain NUL bytes.
16///
17/// - **Rev 1** — `u16 revision || VecMap<Strand, Value>` (length-prefixed sorted entries).
18///   Byte-identical to the legacy on-disk encoding.
19/// - **Rev 2** — optimised envelope (`u16 revision || u32_le payload_length`), inner `VecMap`
20///   written via the indexed-map prologue past `OFFSET_TABLE_MIN_LEN = 8`. Walker descent stays
21///   zero-allocation: `walk_field_0()` on a `Wire`-repr parent borrows the field bytes directly by
22///   bracketing `skip_indexed_map` with `BorrowedReader::remaining()` snapshots, no decode +
23///   re-encode required.
24#[revisioned(revision(1), revision(2, optimised))]
25#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash, Encode, BorrowDecode)]
26#[storekey(format = "()")]
27#[storekey(format = "IndexFormat")]
28pub struct Object(#[revision(indexed_map)] pub VecMap<Strand, Value>);
29
30impl From<BTreeMap<&str, Value>> for Object {
31	fn from(v: BTreeMap<&str, Value>) -> Self {
32		let mut entries = Vec::with_capacity(v.len());
33		entries.extend(v.into_iter().map(|(k, val)| (Strand::from(k), val)));
34		Self(VecMap::from_sorted_vec_unchecked(entries))
35	}
36}
37
38impl From<BTreeMap<String, Value>> for Object {
39	fn from(v: BTreeMap<String, Value>) -> Self {
40		let mut entries = Vec::with_capacity(v.len());
41		entries.extend(v.into_iter().map(|(k, val)| (Strand::from(k), val)));
42		Self(VecMap::from_sorted_vec_unchecked(entries))
43	}
44}
45
46impl From<BTreeMap<Strand, Value>> for Object {
47	fn from(v: BTreeMap<Strand, Value>) -> Self {
48		Self(VecMap::from(v))
49	}
50}
51
52impl From<VecMap<Strand, Value>> for Object {
53	fn from(v: VecMap<Strand, Value>) -> Self {
54		Self(v)
55	}
56}
57
58impl From<VecMap<String, Value>> for Object {
59	fn from(v: VecMap<String, Value>) -> Self {
60		let mut entries = Vec::with_capacity(v.len());
61		entries.extend(v.into_iter().map(|(k, val)| (Strand::from(k), val)));
62		Self(VecMap::from_sorted_vec_unchecked(entries))
63	}
64}
65
66impl From<VecMap<&str, Value>> for Object {
67	fn from(v: VecMap<&str, Value>) -> Self {
68		let mut entries = Vec::with_capacity(v.len());
69		entries.extend(v.into_iter().map(|(k, val)| (Strand::from(k), val)));
70		Self(VecMap::from_sorted_vec_unchecked(entries))
71	}
72}
73
74impl FromIterator<(String, Value)> for Object {
75	fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
76		Self(VecMap::from_iter(iter.into_iter().map(|(k, v)| (Strand::from(k), v))))
77	}
78}
79
80impl FromIterator<(Strand, Value)> for Object {
81	fn from_iter<T: IntoIterator<Item = (Strand, Value)>>(iter: T) -> Self {
82		Self(VecMap::from_iter(iter))
83	}
84}
85
86impl<'a> FromIterator<(&'a str, Value)> for Object {
87	fn from_iter<T: IntoIterator<Item = (&'a str, Value)>>(iter: T) -> Self {
88		Self(VecMap::from_iter(iter.into_iter().map(|(k, v)| (Strand::from(k), v))))
89	}
90}
91
92impl From<BTreeMap<String, String>> for Object {
93	fn from(v: BTreeMap<String, String>) -> Self {
94		let mut entries = Vec::with_capacity(v.len());
95		entries.extend(v.into_iter().map(|(k, v)| (Strand::from(k), Value::from(v))));
96		Self(VecMap::from_sorted_vec_unchecked(entries))
97	}
98}
99
100impl From<Vec<(String, Value)>> for Object {
101	fn from(v: Vec<(String, Value)>) -> Self {
102		Self(VecMap::from_iter(v.into_iter().map(|(k, val)| (Strand::from(k), val))))
103	}
104}
105
106impl From<HashMap<&str, Value>> for Object {
107	fn from(v: HashMap<&str, Value>) -> Self {
108		Self(VecMap::from_iter(v.into_iter().map(|(key, val)| (Strand::from(key), val))))
109	}
110}
111
112impl From<HashMap<String, Value>> for Object {
113	fn from(v: HashMap<String, Value>) -> Self {
114		Self(VecMap::from_iter(v.into_iter().map(|(k, val)| (Strand::from(k), val))))
115	}
116}
117
118impl TryFrom<Object> for crate::types::PublicObject {
119	type Error = anyhow::Error;
120
121	fn try_from(s: Object) -> Result<Self, Self::Error> {
122		s.0.into_iter()
123			.map(|(k, v)| crate::types::PublicValue::try_from(v).map(|v| (k.into_string(), v)))
124			.collect()
125	}
126}
127
128impl From<crate::types::PublicObject> for Object {
129	fn from(s: crate::types::PublicObject) -> Self {
130		let mut entries = Vec::with_capacity(s.len());
131		entries.extend(s.into_iter().map(|(k, v)| (Strand::from(k), Value::from(v))));
132		Self(VecMap::from_sorted_vec_unchecked(entries))
133	}
134}
135
136impl Deref for Object {
137	type Target = VecMap<Strand, Value>;
138	fn deref(&self) -> &Self::Target {
139		&self.0
140	}
141}
142
143impl DerefMut for Object {
144	fn deref_mut(&mut self) -> &mut Self::Target {
145		&mut self.0
146	}
147}
148
149impl IntoIterator for Object {
150	type Item = (Strand, Value);
151	type IntoIter = VecMapIntoIter<Strand, Value>;
152	fn into_iter(self) -> Self::IntoIter {
153		self.0.into_iter()
154	}
155}
156
157impl TryInto<BTreeMap<String, String>> for Object {
158	type Error = anyhow::Error;
159	fn try_into(self) -> Result<BTreeMap<String, String>, Self::Error> {
160		self.into_iter().map(|(k, v)| Ok((k.into_string(), v.coerce_to()?))).collect()
161	}
162}
163
164impl TryInto<HeaderMap> for Object {
165	type Error = anyhow::Error;
166	fn try_into(self) -> Result<HeaderMap, Self::Error> {
167		let mut headermap = HeaderMap::new();
168		for (k, v) in self {
169			let k: HeaderName = k.as_str().parse()?;
170			let v: HeaderValue = v.coerce_to::<String>()?.parse()?;
171			headermap.insert(k, v);
172		}
173
174		Ok(headermap)
175	}
176}
177
178impl Object {
179	/// Insert a key-value pair into the object.
180	///
181	/// The key is accepted as anything convertible to [`Strand`]
182	/// (including `String`, `&str`, and `Strand`), which keeps call
183	/// sites ergonomic.
184	#[inline]
185	pub fn insert(&mut self, key: impl Into<Strand>, value: Value) -> Option<Value> {
186		self.0.insert(key.into(), value)
187	}
188
189	/// Look up a value by key.
190	///
191	/// Takes `&str`, so `&String` callers work transparently via deref
192	/// coercion — avoiding the `Borrow<String>` trait bound that would
193	/// otherwise be required on `Strand`.
194	#[inline]
195	pub fn get(&self, key: &str) -> Option<&Value> {
196		self.0.get(key)
197	}
198
199	/// Look up a value by key for mutation.
200	#[inline]
201	pub fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
202		self.0.get_mut(key)
203	}
204
205	/// Check whether the object contains a given key.
206	#[inline]
207	pub fn contains_key(&self, key: &str) -> bool {
208		self.0.contains_key(key)
209	}
210
211	/// Remove and return the value for `key`.
212	#[inline]
213	pub fn remove(&mut self, key: &str) -> Option<Value> {
214		self.0.remove(key)
215	}
216
217	/// Return the map entry for `key`, so callers can use the
218	/// `Entry` API without manual interning.
219	#[inline]
220	pub fn entry(&mut self, key: impl Into<Strand>) -> VecMapEntry<'_, Strand, Value> {
221		self.0.entry(key.into())
222	}
223
224	/// Fetch the record id if there is one
225	pub fn rid(&self) -> Option<RecordId> {
226		match self.get("id") {
227			Some(Value::RecordId(v)) => Some(v.clone()),
228			_ => None,
229		}
230	}
231
232	pub fn into_literal(self) -> Vec<ObjectEntry> {
233		self.0
234			.into_iter()
235			.map(|(k, v)| ObjectEntry {
236				key: k,
237				value: v.into_literal(),
238			})
239			.collect()
240	}
241}
242
243impl std::ops::Add for Object {
244	type Output = Self;
245
246	fn add(self, rhs: Self) -> Self::Output {
247		Self(VecMap::merge_sorted_prefer_rhs(self.0, rhs.0))
248	}
249}
250
251impl ToSql for Object {
252	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
253		if self.is_empty() {
254			return f.push_str("{  }");
255		}
256
257		if sql_fmt.is_pretty() {
258			f.push('{');
259		} else {
260			f.push_str("{ ");
261		}
262
263		if !self.is_empty() {
264			let inner_fmt = sql_fmt.increment();
265			if sql_fmt.is_pretty() {
266				f.push('\n');
267				inner_fmt.write_indent(f);
268			}
269			for (i, (key, value)) in self.0.iter().enumerate() {
270				if i > 0 {
271					inner_fmt.write_separator(f);
272				}
273				write_sql!(f, sql_fmt, "{}: ", EscapeObjectKey(key.as_str()));
274				value.fmt_sql(f, inner_fmt);
275			}
276			if sql_fmt.is_pretty() {
277				f.push('\n');
278				sql_fmt.write_indent(f);
279			}
280		}
281
282		if sql_fmt.is_pretty() {
283			f.push('}');
284		} else {
285			f.push_str(" }");
286		}
287	}
288}