surrealdb_core/sql/
idiom.rs

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