surrealdb_sql/
idiom.rs

1use crate::ctx::Context;
2use crate::dbs::{Options, Transaction};
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::fmt::{fmt_separated_by, Fmt};
6use crate::part::Next;
7use crate::part::Part;
8use crate::paths::{ID, IN, META, OUT};
9use crate::value::Value;
10use md5::Digest;
11use md5::Md5;
12use revision::revisioned;
13use serde::{Deserialize, Serialize};
14use std::fmt::{self, Display, Formatter};
15use std::ops::Deref;
16use std::str;
17
18pub(crate) const TOKEN: &str = "$surrealdb::private::crate::Idiom";
19
20#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
21#[revisioned(revision = 1)]
22pub struct Idioms(pub Vec<Idiom>);
23
24impl Deref for Idioms {
25	type Target = Vec<Idiom>;
26	fn deref(&self) -> &Self::Target {
27		&self.0
28	}
29}
30
31impl IntoIterator for Idioms {
32	type Item = Idiom;
33	type IntoIter = std::vec::IntoIter<Self::Item>;
34	fn into_iter(self) -> Self::IntoIter {
35		self.0.into_iter()
36	}
37}
38
39impl Display for Idioms {
40	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
41		Display::fmt(&Fmt::comma_separated(&self.0), f)
42	}
43}
44
45#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
46#[serde(rename = "$surrealdb::private::crate::Idiom")]
47#[revisioned(revision = 1)]
48pub struct Idiom(pub Vec<Part>);
49
50impl Deref for Idiom {
51	type Target = [Part];
52	fn deref(&self) -> &Self::Target {
53		self.0.as_slice()
54	}
55}
56
57impl From<String> for Idiom {
58	fn from(v: String) -> Self {
59		Self(vec![Part::from(v)])
60	}
61}
62
63impl From<Vec<Part>> for Idiom {
64	fn from(v: Vec<Part>) -> Self {
65		Self(v)
66	}
67}
68
69impl From<&[Part]> for Idiom {
70	fn from(v: &[Part]) -> Self {
71		Self(v.to_vec())
72	}
73}
74
75impl Idiom {
76	/// Appends a part to the end of this Idiom
77	pub(crate) fn push(mut self, n: Part) -> Idiom {
78		self.0.push(n);
79		self
80	}
81	/// Convert this Idiom to a unique hash
82	pub(crate) fn to_hash(&self) -> String {
83		let mut hasher = Md5::new();
84		hasher.update(self.to_string().as_str());
85		format!("{:x}", hasher.finalize())
86	}
87	/// Convert this Idiom to a JSON Path string
88	pub(crate) fn to_path(&self) -> String {
89		format!("/{self}").replace(']', "").replace(&['.', '['][..], "/")
90	}
91	/// Simplifies this Idiom for use in object keys
92	pub(crate) fn simplify(&self) -> Idiom {
93		self.0
94			.iter()
95			.filter(|&p| {
96				matches!(p, Part::Field(_) | Part::Start(_) | Part::Value(_) | Part::Graph(_))
97			})
98			.cloned()
99			.collect::<Vec<_>>()
100			.into()
101	}
102	/// Check if this Idiom is an 'id' field
103	pub(crate) fn is_id(&self) -> bool {
104		self.0.len() == 1 && self.0[0].eq(&ID[0])
105	}
106	/// Check if this Idiom is an 'in' field
107	pub(crate) fn is_in(&self) -> bool {
108		self.0.len() == 1 && self.0[0].eq(&IN[0])
109	}
110	/// Check if this Idiom is an 'out' field
111	pub(crate) fn is_out(&self) -> bool {
112		self.0.len() == 1 && self.0[0].eq(&OUT[0])
113	}
114	/// Check if this Idiom is a 'meta' field
115	pub(crate) fn is_meta(&self) -> bool {
116		self.0.len() == 1 && self.0[0].eq(&META[0])
117	}
118	/// Check if this is an expression with multiple yields
119	pub(crate) fn is_multi_yield(&self) -> bool {
120		self.iter().any(Self::split_multi_yield)
121	}
122	/// Check if the path part is a yield in a multi-yield expression
123	pub(crate) fn split_multi_yield(v: &Part) -> bool {
124		matches!(v, Part::Graph(g) if g.alias.is_some())
125	}
126}
127
128impl Idiom {
129	/// Check if we require a writeable transaction
130	pub(crate) fn writeable(&self) -> bool {
131		self.0.iter().any(|v| v.writeable())
132	}
133	/// Process this type returning a computed simple Value
134	pub(crate) async fn compute(
135		&self,
136		ctx: &Context<'_>,
137		opt: &Options,
138		txn: &Transaction,
139		doc: Option<&CursorDoc<'_>>,
140	) -> Result<Value, Error> {
141		match self.first() {
142			// The starting part is a value
143			Some(Part::Start(v)) => {
144				v.compute(ctx, opt, txn, doc)
145					.await?
146					.get(ctx, opt, txn, doc, self.as_ref().next())
147					.await?
148					.compute(ctx, opt, txn, doc)
149					.await
150			}
151			// Otherwise use the current document
152			_ => match doc {
153				// There is a current document
154				Some(v) => {
155					v.doc.get(ctx, opt, txn, doc, self).await?.compute(ctx, opt, txn, doc).await
156				}
157				// There isn't any document
158				None => Ok(Value::None),
159			},
160		}
161	}
162}
163
164impl Display for Idiom {
165	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
166		Display::fmt(
167			&Fmt::new(
168				self.0.iter().enumerate().map(|args| {
169					Fmt::new(args, |(i, p), f| match (i, p) {
170						(0, Part::Field(v)) => Display::fmt(v, f),
171						_ => Display::fmt(p, f),
172					})
173				}),
174				fmt_separated_by(""),
175			),
176			f,
177		)
178	}
179}