surrealdb_core/sql/
thing.rs

1use super::id::range::IdRange;
2use super::{Cond, Expression, Ident, Idiom, Operator, Part, Table};
3use crate::ctx::Context;
4use crate::dbs::Options;
5use crate::doc::CursorDoc;
6use crate::err::Error;
7use crate::key::r#ref::Ref;
8use crate::kvs::KeyDecode as _;
9use crate::sql::{escape::escape_rid, id::Id, Strand, Value};
10use crate::syn;
11use futures::StreamExt;
12use reblessive::tree::Stk;
13use revision::revisioned;
14use serde::{Deserialize, Serialize};
15use std::fmt;
16use std::ops::Bound;
17use std::str::FromStr;
18
19const ID: &str = "id";
20pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
21
22#[revisioned(revision = 1)]
23#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
24#[serde(rename = "$surrealdb::private::sql::Thing")]
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26#[non_exhaustive]
27pub struct Thing {
28	/// Table name
29	pub tb: String,
30	pub id: Id,
31}
32
33impl Thing {
34	/// Convert `Thing` to `Cond`
35	pub fn to_cond(self) -> Option<Cond> {
36		match &self.id {
37			Id::Range(r) => match (&r.beg, &r.end) {
38				(Bound::Unbounded, Bound::Unbounded) => None,
39				(Bound::Unbounded, Bound::Excluded(id)) => {
40					Some(Cond(Value::Expression(Box::new(Expression::new(
41						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
42						Operator::LessThan,
43						Thing::from((self.tb, id.to_owned())).into(),
44					)))))
45				}
46				(Bound::Unbounded, Bound::Included(id)) => {
47					Some(Cond(Value::Expression(Box::new(Expression::new(
48						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
49						Operator::LessThanOrEqual,
50						Thing::from((self.tb, id.to_owned())).into(),
51					)))))
52				}
53				(Bound::Excluded(id), Bound::Unbounded) => {
54					Some(Cond(Value::Expression(Box::new(Expression::new(
55						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
56						Operator::MoreThan,
57						Thing::from((self.tb, id.to_owned())).into(),
58					)))))
59				}
60				(Bound::Included(id), Bound::Unbounded) => {
61					Some(Cond(Value::Expression(Box::new(Expression::new(
62						Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
63						Operator::MoreThanOrEqual,
64						Thing::from((self.tb, id.to_owned())).into(),
65					)))))
66				}
67				(Bound::Included(lid), Bound::Included(rid)) => {
68					Some(Cond(Value::Expression(Box::new(Expression::new(
69						Value::Expression(Box::new(Expression::new(
70							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
71							Operator::MoreThanOrEqual,
72							Thing::from((self.tb.clone(), lid.to_owned())).into(),
73						))),
74						Operator::And,
75						Value::Expression(Box::new(Expression::new(
76							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
77							Operator::LessThanOrEqual,
78							Thing::from((self.tb, rid.to_owned())).into(),
79						))),
80					)))))
81				}
82				(Bound::Included(lid), Bound::Excluded(rid)) => {
83					Some(Cond(Value::Expression(Box::new(Expression::new(
84						Value::Expression(Box::new(Expression::new(
85							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
86							Operator::MoreThanOrEqual,
87							Thing::from((self.tb.clone(), lid.to_owned())).into(),
88						))),
89						Operator::And,
90						Value::Expression(Box::new(Expression::new(
91							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
92							Operator::LessThan,
93							Thing::from((self.tb, rid.to_owned())).into(),
94						))),
95					)))))
96				}
97				(Bound::Excluded(lid), Bound::Included(rid)) => {
98					Some(Cond(Value::Expression(Box::new(Expression::new(
99						Value::Expression(Box::new(Expression::new(
100							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
101							Operator::MoreThan,
102							Thing::from((self.tb.clone(), lid.to_owned())).into(),
103						))),
104						Operator::And,
105						Value::Expression(Box::new(Expression::new(
106							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
107							Operator::LessThanOrEqual,
108							Thing::from((self.tb, rid.to_owned())).into(),
109						))),
110					)))))
111				}
112				(Bound::Excluded(lid), Bound::Excluded(rid)) => {
113					Some(Cond(Value::Expression(Box::new(Expression::new(
114						Value::Expression(Box::new(Expression::new(
115							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
116							Operator::MoreThan,
117							Thing::from((self.tb.clone(), lid.to_owned())).into(),
118						))),
119						Operator::And,
120						Value::Expression(Box::new(Expression::new(
121							Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
122							Operator::LessThan,
123							Thing::from((self.tb, rid.to_owned())).into(),
124						))),
125					)))))
126				}
127			},
128			_ => Some(Cond(Value::Expression(Box::new(Expression::new(
129				Idiom(vec![Part::from(Ident(ID.to_owned()))]).into(),
130				Operator::Equal,
131				Thing::from((self.tb, self.id)).into(),
132			))))),
133		}
134	}
135
136	pub(crate) async fn refs(
137		&self,
138		ctx: &Context,
139		opt: &Options,
140		ft: Option<&Table>,
141		ff: Option<&Idiom>,
142	) -> Result<Vec<Thing>, Error> {
143		let (ns, db) = opt.ns_db()?;
144
145		let (prefix, suffix) = match (ft, ff) {
146			(Some(ft), Some(ff)) => {
147				let ff = ff.to_string();
148
149				(
150					crate::key::r#ref::ffprefix(ns, db, &self.tb, &self.id, ft, &ff),
151					crate::key::r#ref::ffsuffix(ns, db, &self.tb, &self.id, ft, &ff),
152				)
153			}
154			(Some(ft), None) => (
155				crate::key::r#ref::ftprefix(ns, db, &self.tb, &self.id, ft),
156				crate::key::r#ref::ftsuffix(ns, db, &self.tb, &self.id, ft),
157			),
158			(None, None) => (
159				crate::key::r#ref::prefix(ns, db, &self.tb, &self.id),
160				crate::key::r#ref::suffix(ns, db, &self.tb, &self.id),
161			),
162			(None, Some(_)) => {
163				return Err(Error::Unreachable(
164					"A foreign field was passed without a foreign table".into(),
165				))
166			}
167		};
168
169		let range = prefix?..suffix?;
170		let txn = ctx.tx();
171		let mut stream = txn.stream_keys(range, None);
172
173		// Collect the keys from the stream into a vec
174		let mut keys: Vec<Vec<u8>> = vec![];
175		while let Some(res) = stream.next().await {
176			keys.push(res?);
177		}
178
179		let mut ids = Vec::new();
180		for x in keys.iter() {
181			let key = Ref::decode(x)?;
182			ids.push(Thing {
183				tb: key.ft.to_string(),
184				id: key.fk,
185			})
186		}
187
188		Ok(ids)
189	}
190}
191
192impl From<(&str, Id)> for Thing {
193	fn from((tb, id): (&str, Id)) -> Self {
194		Self {
195			tb: tb.to_owned(),
196			id,
197		}
198	}
199}
200
201impl From<(String, Id)> for Thing {
202	fn from((tb, id): (String, Id)) -> Self {
203		Self {
204			tb,
205			id,
206		}
207	}
208}
209
210impl From<(&str, IdRange)> for Thing {
211	fn from((tb, id): (&str, IdRange)) -> Self {
212		Self {
213			tb: tb.to_owned(),
214			id: id.into(),
215		}
216	}
217}
218
219impl From<(String, IdRange)> for Thing {
220	fn from((tb, id): (String, IdRange)) -> Self {
221		Self {
222			tb,
223			id: id.into(),
224		}
225	}
226}
227
228impl From<(String, String)> for Thing {
229	fn from((tb, id): (String, String)) -> Self {
230		Self::from((tb, Id::from(id)))
231	}
232}
233
234impl From<(&str, &str)> for Thing {
235	fn from((tb, id): (&str, &str)) -> Self {
236		Self::from((tb.to_owned(), Id::from(id)))
237	}
238}
239
240impl FromStr for Thing {
241	type Err = ();
242	fn from_str(s: &str) -> Result<Self, Self::Err> {
243		Self::try_from(s)
244	}
245}
246
247impl TryFrom<String> for Thing {
248	type Error = ();
249	fn try_from(v: String) -> Result<Self, Self::Error> {
250		Self::try_from(v.as_str())
251	}
252}
253
254impl TryFrom<Strand> for Thing {
255	type Error = ();
256	fn try_from(v: Strand) -> Result<Self, Self::Error> {
257		Self::try_from(v.as_str())
258	}
259}
260
261impl TryFrom<&str> for Thing {
262	type Error = ();
263	fn try_from(v: &str) -> Result<Self, Self::Error> {
264		match syn::thing_with_range(v) {
265			Ok(v) => Ok(v),
266			_ => Err(()),
267		}
268	}
269}
270
271impl Thing {
272	/// Convert the Thing to a raw String
273	pub fn to_raw(&self) -> String {
274		self.to_string()
275	}
276	/// Check if this Thing is a range
277	pub fn is_range(&self) -> bool {
278		matches!(self.id, Id::Range(_))
279	}
280	/// Check if this Thing is of a certain table type
281	pub fn is_record_type(&self, types: &[Table]) -> bool {
282		types.is_empty() || types.iter().any(|tb| tb.0 == self.tb)
283	}
284}
285
286impl fmt::Display for Thing {
287	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288		write!(f, "{}:{}", escape_rid(&self.tb), self.id)
289	}
290}
291
292impl Thing {
293	/// Process this type returning a computed simple Value
294	pub(crate) async fn compute(
295		&self,
296		stk: &mut Stk,
297		ctx: &Context,
298		opt: &Options,
299		doc: Option<&CursorDoc>,
300	) -> Result<Value, Error> {
301		Ok(Value::Thing(Thing {
302			tb: self.tb.clone(),
303			id: self.id.compute(stk, ctx, opt, doc).await?,
304		}))
305	}
306}
307
308#[cfg(test)]
309mod test {
310	use std::{ops::Bound, str::FromStr};
311
312	use crate::sql::{Array, Id, IdRange, Object, Value};
313
314	use super::Thing;
315
316	#[test]
317	fn from() {
318		{
319			let string = "foo:bar";
320			let thing = Thing {
321				tb: "foo".into(),
322				id: Id::String("bar".into()),
323			};
324			assert_eq!(thing, Thing::from_str(string).unwrap());
325		}
326		{
327			let string = "foo:1";
328			let thing = Thing {
329				tb: "foo".into(),
330				id: Id::Number(1),
331			};
332			assert_eq!(thing, Thing::from_str(string).unwrap());
333		}
334		{
335			let string = "foo:[1, 'bar']";
336			let thing = Thing {
337				tb: "foo".into(),
338				id: Id::Array(Array(vec![1i64.into(), "bar".into()])),
339			};
340			assert_eq!(thing, Thing::from_str(string).unwrap());
341		}
342		{
343			let string = "foo:{bar: 1}";
344			let thing = Thing {
345				tb: "foo".into(),
346				id: Id::Object(Object([("bar".to_string(), Value::from(1))].into_iter().collect())),
347			};
348			assert_eq!(thing, Thing::from_str(string).unwrap());
349		}
350		{
351			let string = "foo:1..=2";
352			let thing = Thing {
353				tb: "foo".into(),
354				id: Id::Range(Box::new(
355					IdRange::try_from((
356						Bound::Included(Id::Number(1)),
357						Bound::Included(Id::Number(2)),
358					))
359					.unwrap(),
360				)),
361			};
362			assert_eq!(thing, Thing::from_str(string).unwrap());
363		}
364	}
365}