Skip to main content

surrealdb_expr/expr/
part.rs

1use std::cmp::Ordering;
2
3use common::fmt::EscapeKwFreeIdent;
4use surrealdb_strand::Strand;
5use surrealdb_types::{SqlFormat, ToSql};
6
7use crate::expr::method::is_builtin_method;
8use crate::expr::{Expr, Idiom, Literal, Lookup};
9
10#[derive(Clone, Debug, Eq, PartialEq, Hash)]
11pub enum Part {
12	All,
13	Flatten,
14	Last,
15	First,
16	Field(Strand),
17	Where(Expr),
18	Lookup(Box<Lookup>),
19	Value(Expr),
20	/// TODO: Remove, start and move it out of part to eliminate invalid state.
21	Start(Expr),
22	Method(Strand, Vec<Expr>),
23	Destructure(Vec<DestructurePart>),
24	Optional,
25	Recurse(Recurse, Option<Idiom>, Option<RecurseInstruction>),
26	Doc,
27	RepeatRecurse,
28}
29
30impl Part {
31	/// Returns a part which is equivalent to `[1]` if called with integer `1`.
32	pub fn index_int(idx: i64) -> Self {
33		Part::Value(Expr::Literal(Literal::Integer(idx)))
34	}
35
36	pub fn is_index(&self) -> bool {
37		matches!(self, Part::Value(Expr::Literal(Literal::Integer(_))) | Part::First | Part::Last)
38	}
39
40	/// Returns the idex if this part would have been `Part::Index(x)` before
41	/// that field was removed.
42	///
43	/// TODO: Remove this method once we work out the kinks with removing
44	/// `Part::Index(x)` and only having `Part::Value(x)`
45	///
46	/// Already marked as deprecated for the full release to remind that this
47	/// behavior should be fixed.
48	pub fn as_old_index(&self) -> Option<usize> {
49		match self {
50			Part::Value(Expr::Literal(l)) => match l {
51				crate::expr::Literal::Integer(i) => Some(*i as usize),
52				crate::expr::Literal::Float(f) => Some(*f as usize),
53				crate::expr::Literal::Decimal(d) => Some(usize::try_from(*d).unwrap_or_default()),
54				_ => None,
55			},
56			_ => None,
57		}
58	}
59
60	/// Whether evaluating this part can be done on a read-only transaction.
61	///
62	/// Matched exhaustively on purpose: a new variant that carries an
63	/// expression must be classified here rather than defaulting to read-only,
64	/// which would let a mutation reach a read-only transaction.
65	pub fn read_only(&self) -> bool {
66		match self {
67			Part::Start(v) => v.read_only(),
68			Part::Where(v) => v.read_only(),
69			Part::Value(v) => v.read_only(),
70			// A builtin method writes nothing beyond what its arguments
71			// carry, but an unregistered name invokes a closure stored on
72			// the receiver — a runtime value whose body is invisible here —
73			// so it must over-approximate to writable.
74			Part::Method(name, v) => is_builtin_method(name) && v.iter().all(Expr::read_only),
75			Part::Lookup(v) => v.read_only(),
76			Part::Destructure(v) => v.iter().all(DestructurePart::read_only),
77			Part::Recurse(_, alias, instruction) => {
78				alias.as_ref().map(|x| x.read_only()).unwrap_or(true)
79					&& instruction.as_ref().map(|x| x.read_only()).unwrap_or(true)
80			}
81			Part::All
82			| Part::Flatten
83			| Part::Last
84			| Part::First
85			| Part::Field(_)
86			| Part::Optional
87			| Part::Doc
88			| Part::RepeatRecurse => true,
89		}
90	}
91	/// Returns a yield if an alias is specified
92	pub fn alias(&self) -> Option<&Idiom> {
93		match self {
94			Part::Lookup(v) => v.alias.as_ref(),
95			_ => None,
96		}
97	}
98
99	fn recursion_plan(&self) -> Option<RecursionPlan> {
100		match self {
101			Part::RepeatRecurse => Some(RecursionPlan::Repeat),
102			Part::Destructure(parts) => {
103				for (j, p) in parts.iter().enumerate() {
104					let plan = match p {
105						DestructurePart::Aliased(field, v) => v.find_recursion_plan().map(|plan| {
106							(
107								field.to_owned(),
108								plan.0.to_vec(),
109								Box::new(plan.1.clone()),
110								plan.2.to_vec(),
111							)
112						}),
113						DestructurePart::Destructure(field, parts) => {
114							Part::Destructure(parts.to_owned()).recursion_plan().map(|plan| {
115								(
116									field.to_owned(),
117									vec![Part::Field(field.to_owned())],
118									Box::new(plan),
119									vec![],
120								)
121							})
122						}
123						_ => None,
124					};
125
126					if let Some((field, before, plan, after)) = plan {
127						let mut parts = parts.clone();
128						parts.remove(j);
129						return Some(RecursionPlan::Destructure {
130							parts,
131							field,
132							before,
133							plan,
134							after,
135						});
136					}
137				}
138
139				None
140			}
141			_ => None,
142		}
143	}
144
145	pub fn to_raw_string(&self) -> String {
146		match self {
147			Part::Start(v) => v.to_raw_string(),
148			Part::Field(v) => {
149				let mut s = ".".to_string();
150				EscapeKwFreeIdent(v.as_str()).fmt_sql(&mut s, SqlFormat::SingleLine);
151				s
152			}
153			_ => self.to_sql(),
154		}
155	}
156
157	// Helper function to get a numeric discriminant for ordering
158	fn discriminant_value(&self) -> u8 {
159		match self {
160			Part::Field(_) => 0,
161			Part::All => 1,
162			Part::Flatten => 2,
163			Part::Last => 3,
164			Part::First => 4,
165			Part::Where(_) => 5,
166			Part::Lookup(_) => 6,
167			Part::Value(_) => 7,
168			Part::Start(_) => 8,
169			Part::Method(_, _) => 9,
170			Part::Destructure(_) => 10,
171			Part::Optional => 11,
172			Part::Recurse(_, _, _) => 12,
173			Part::Doc => 13,
174			Part::RepeatRecurse => 14,
175		}
176	}
177}
178
179impl ToSql for Part {
180	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
181		let part: crate::sql::part::Part = self.clone().into();
182		part.fmt_sql(f, fmt);
183	}
184}
185
186impl PartialOrd for Part {
187	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
188		let self_disc = self.discriminant_value();
189		let other_disc = other.discriminant_value();
190
191		match self_disc.cmp(&other_disc) {
192			Ordering::Equal => {
193				// Same variant, compare by content
194				match (self, other) {
195					(Part::Field(a), Part::Field(b)) => a.partial_cmp(b),
196					(Part::Method(name_a, args_a), Part::Method(name_b, args_b)) => {
197						// Compare method name first, then argument count
198						match name_a.partial_cmp(name_b) {
199							Some(Ordering::Equal) => args_a.len().partial_cmp(&args_b.len()),
200							other => other,
201						}
202					}
203					// For variants without meaningful internal ordering, consider them equal
204					// when they're the same variant (All, Flatten, Last, First, Optional, Doc,
205					// RepeatRecurse)
206					(Part::All, Part::All)
207					| (Part::Flatten, Part::Flatten)
208					| (Part::Last, Part::Last)
209					| (Part::First, Part::First)
210					| (Part::Optional, Part::Optional)
211					| (Part::Doc, Part::Doc)
212					| (Part::RepeatRecurse, Part::RepeatRecurse) => Some(Ordering::Equal),
213					// For complex variants (Where, Lookup, Value, Start, Destructure, Recurse),
214					// we can't easily compare their contents, so consider them equal when same
215					// variant This is acceptable for FETCH clause sorting since these are
216					// rarely used
217					(Part::Where(_), Part::Where(_))
218					| (Part::Lookup(_), Part::Lookup(_))
219					| (Part::Value(_), Part::Value(_))
220					| (Part::Start(_), Part::Start(_))
221					| (Part::Destructure(_), Part::Destructure(_))
222					| (Part::Recurse(_, _, _), Part::Recurse(_, _, _)) => Some(Ordering::Equal),
223
224					_ => None,
225				}
226			}
227			ordering => Some(ordering),
228		}
229	}
230}
231
232// ------------------------------
233
234#[derive(Clone, Debug)]
235pub enum RecursionPlan {
236	Repeat,
237	Destructure {
238		// The destructure parts
239		parts: Vec<DestructurePart>,
240		// Which field contains the repeat symbol
241		field: Strand,
242		// Path before the repeat symbol
243		before: Vec<Part>,
244		// The recursion plan
245		plan: Box<RecursionPlan>,
246		// Path after the repeat symbol
247		after: Vec<Part>,
248	},
249}
250
251pub trait FindRecursionPlan<'a> {
252	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])>;
253}
254
255impl<'a> FindRecursionPlan<'a> for &'a [Part] {
256	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])> {
257		for (i, p) in self.iter().enumerate() {
258			if let Some(plan) = p.recursion_plan() {
259				return Some((&self[..i], plan, &self[(i + 1)..]));
260			}
261		}
262
263		None
264	}
265}
266
267impl<'a> FindRecursionPlan<'a> for &'a Idiom {
268	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])> {
269		for (i, p) in self.iter().enumerate() {
270			if let Some(plan) = p.recursion_plan() {
271				return Some((&self[..i], plan, &self[(i + 1)..]));
272			}
273		}
274
275		None
276	}
277}
278
279// ------------------------------
280
281pub trait SplitByRepeatRecurse<'a> {
282	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])>;
283}
284
285impl<'a> SplitByRepeatRecurse<'a> for &'a [Part] {
286	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])> {
287		self.iter()
288			.position(|p| matches!(p, Part::RepeatRecurse))
289			// We exclude the `@` repeat recurse symbol here, because
290			// it ensures we will loop the idiom path, instead of using
291			// `.get()` to recurse
292			.map(|i| (&self[..i], &self[(i + 1)..]))
293	}
294}
295
296impl<'a> SplitByRepeatRecurse<'a> for &'a Idiom {
297	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])> {
298		self.iter()
299			.position(|p| matches!(p, Part::RepeatRecurse))
300			// We exclude the `@` repeat recurse symbol here, because
301			// it ensures we will loop the idiom path, instead of using
302			// `.get()` to recurse
303			.map(|i| (&self[..i], &self[(i + 1)..]))
304	}
305}
306
307// ------------------------------
308
309pub trait Next<'a> {
310	fn next(&'a self) -> &'a [Part];
311}
312
313impl<'a> Next<'a> for &'a [Part] {
314	fn next(&'a self) -> &'a [Part] {
315		match self.len() {
316			0 => &[],
317			_ => &self[1..],
318		}
319	}
320}
321
322// ------------------------------
323
324pub trait NextMethod<'a> {
325	fn next_method(&'a self) -> &'a [Part];
326}
327
328impl<'a> NextMethod<'a> for &'a [Part] {
329	fn next_method(&'a self) -> &'a [Part] {
330		match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
331			None => &[],
332			Some(i) => &self[i..],
333		}
334	}
335}
336
337impl<'a> NextMethod<'a> for &'a Idiom {
338	fn next_method(&'a self) -> &'a [Part] {
339		match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
340			None => &[],
341			Some(i) => &self[i..],
342		}
343	}
344}
345
346// ------------------------------
347
348#[derive(Clone, Debug, Eq, PartialEq, Hash)]
349pub enum DestructurePart {
350	All(Strand),
351	Field(Strand),
352	Aliased(Strand, Idiom),
353	Destructure(Strand, Vec<DestructurePart>),
354}
355
356impl DestructurePart {
357	/// Whether evaluating this destructure entry can be done on a read-only
358	/// transaction.
359	pub fn read_only(&self) -> bool {
360		match self {
361			DestructurePart::All(_) | DestructurePart::Field(_) => true,
362			DestructurePart::Aliased(_, v) => v.read_only(),
363			DestructurePart::Destructure(_, v) => v.iter().all(DestructurePart::read_only),
364		}
365	}
366
367	pub fn field(&self) -> &str {
368		match self {
369			DestructurePart::All(v) => v.as_str(),
370			DestructurePart::Field(v) => v.as_str(),
371			DestructurePart::Aliased(v, _) => v.as_str(),
372			DestructurePart::Destructure(v, _) => v.as_str(),
373		}
374	}
375
376	pub fn path(&self) -> Vec<Part> {
377		match self {
378			DestructurePart::All(v) => vec![Part::Field(v.clone()), Part::All],
379			DestructurePart::Field(v) => vec![Part::Field(v.clone())],
380			DestructurePart::Aliased(_, v) => v.0.clone(),
381			DestructurePart::Destructure(f, d) => {
382				vec![Part::Field(f.clone()), Part::Destructure(d.clone())]
383			}
384		}
385	}
386
387	pub fn idiom(&self) -> Idiom {
388		Idiom(self.path())
389	}
390}
391
392impl ToSql for DestructurePart {
393	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
394		let stmt: crate::sql::part::DestructurePart = self.clone().into();
395		stmt.fmt_sql(f, fmt);
396	}
397}
398
399// ------------------------------
400
401#[derive(Clone, Debug, Eq, PartialEq, Hash)]
402pub enum Recurse {
403	Fixed(u32),
404	Range(Option<u32>, Option<u32>),
405}
406
407impl ToSql for Recurse {
408	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
409		let recurse: crate::sql::part::Recurse = self.clone().into();
410		recurse.fmt_sql(f, fmt);
411	}
412}
413
414// ------------------------------
415
416#[derive(Clone, Debug, Eq, PartialEq, Hash)]
417pub enum RecurseInstruction {
418	Path {
419		// Do we include the starting point in the paths?
420		inclusive: bool,
421	},
422	Collect {
423		// Do we include the starting point in the collection?
424		inclusive: bool,
425	},
426	Shortest {
427		// What ending node are we looking for?
428		expects: Expr,
429		// Do we include the starting point in the collection?
430		inclusive: bool,
431	},
432}
433
434impl RecurseInstruction {
435	/// Whether evaluating this recursion instruction can be done on a
436	/// read-only transaction.
437	pub fn read_only(&self) -> bool {
438		match self {
439			RecurseInstruction::Path {
440				..
441			}
442			| RecurseInstruction::Collect {
443				..
444			} => true,
445			RecurseInstruction::Shortest {
446				expects,
447				..
448			} => expects.read_only(),
449		}
450	}
451}
452
453impl ToSql for RecurseInstruction {
454	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
455		let stmt: crate::sql::part::RecurseInstruction = self.clone().into();
456		stmt.fmt_sql(f, fmt);
457	}
458}