surrealdb_core/sql/
part.rs

1use crate::{
2	cnf::IDIOM_RECURSION_LIMIT,
3	ctx::Context,
4	dbs::Options,
5	doc::CursorDoc,
6	err::Error,
7	exe::try_join_all_buffered,
8	sql::{fmt::Fmt, strand::no_nul_bytes, Graph, Ident, Idiom, Number, Value},
9};
10use reblessive::tree::Stk;
11use revision::revisioned;
12use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::fmt::Write;
15use std::str;
16
17use super::{
18	fmt::{is_pretty, pretty_indent},
19	value::idiom_recursion::{clean_iteration, compute_idiom_recursion, is_final, Recursion},
20};
21
22#[revisioned(revision = 4)]
23#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
24#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
25#[non_exhaustive]
26pub enum Part {
27	All,
28	Flatten,
29	Last,
30	First,
31	Field(Ident),
32	Index(Number),
33	Where(Value),
34	Graph(Graph),
35	Value(Value),
36	Start(Value),
37	Method(#[serde(with = "no_nul_bytes")] String, Vec<Value>),
38	#[revision(start = 2)]
39	Destructure(Vec<DestructurePart>),
40	Optional,
41	#[revision(
42		start = 3,
43		end = 4,
44		convert_fn = "convert_recurse_add_instruction",
45		fields_name = "OldRecurseFields"
46	)]
47	Recurse(Recurse, Option<Idiom>),
48	#[revision(start = 4)]
49	Recurse(Recurse, Option<Idiom>, Option<RecurseInstruction>),
50	#[revision(start = 3)]
51	Doc,
52	#[revision(start = 3)]
53	RepeatRecurse,
54}
55
56impl Part {
57	fn convert_recurse_add_instruction(
58		fields: OldRecurseFields,
59		_revision: u16,
60	) -> Result<Self, revision::Error> {
61		Ok(Part::Recurse(fields.0, fields.1, None))
62	}
63}
64
65impl From<i32> for Part {
66	fn from(v: i32) -> Self {
67		Self::Index(v.into())
68	}
69}
70
71impl From<isize> for Part {
72	fn from(v: isize) -> Self {
73		Self::Index(v.into())
74	}
75}
76
77impl From<usize> for Part {
78	fn from(v: usize) -> Self {
79		Self::Index(v.into())
80	}
81}
82
83impl From<String> for Part {
84	fn from(v: String) -> Self {
85		Self::Field(v.into())
86	}
87}
88
89impl From<Number> for Part {
90	fn from(v: Number) -> Self {
91		Self::Index(v)
92	}
93}
94
95impl From<Ident> for Part {
96	fn from(v: Ident) -> Self {
97		Self::Field(v)
98	}
99}
100
101impl From<Graph> for Part {
102	fn from(v: Graph) -> Self {
103		Self::Graph(v)
104	}
105}
106
107impl From<&str> for Part {
108	fn from(v: &str) -> Self {
109		match v.parse::<isize>() {
110			Ok(v) => Self::from(v),
111			_ => Self::from(v.to_owned()),
112		}
113	}
114}
115
116impl Part {
117	/// Check if we require a writeable transaction
118	pub(crate) fn writeable(&self) -> bool {
119		match self {
120			Part::Start(v) => v.writeable(),
121			Part::Where(v) => v.writeable(),
122			Part::Value(v) => v.writeable(),
123			Part::Method(_, v) => v.iter().any(Value::writeable),
124			_ => false,
125		}
126	}
127	/// Returns a yield if an alias is specified
128	pub(crate) fn alias(&self) -> Option<&Idiom> {
129		match self {
130			Part::Graph(v) => v.alias.as_ref(),
131			_ => None,
132		}
133	}
134
135	fn recursion_plan(&self) -> Option<RecursionPlan> {
136		match self {
137			Part::RepeatRecurse => Some(RecursionPlan::Repeat),
138			Part::Destructure(parts) => {
139				for (j, p) in parts.iter().enumerate() {
140					let plan = match p {
141						DestructurePart::Aliased(field, v) => v.find_recursion_plan().map(|plan| {
142							(
143								field.to_owned(),
144								plan.0.to_vec(),
145								Box::new(plan.1.to_owned()),
146								plan.2.to_vec(),
147							)
148						}),
149						DestructurePart::Destructure(field, parts) => {
150							Part::Destructure(parts.to_owned()).recursion_plan().map(|plan| {
151								(
152									field.to_owned(),
153									vec![Part::Field(field.to_owned())],
154									Box::new(plan),
155									vec![],
156								)
157							})
158						}
159						_ => None,
160					};
161
162					if let Some((field, before, plan, after)) = plan {
163						let mut parts = parts.clone();
164						parts.remove(j);
165						return Some(RecursionPlan::Destructure {
166							parts,
167							field,
168							before,
169							plan,
170							after,
171						});
172					}
173				}
174
175				None
176			}
177			_ => None,
178		}
179	}
180}
181
182impl fmt::Display for Part {
183	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184		match self {
185			Part::All => f.write_str("[*]"),
186			Part::Last => f.write_str("[$]"),
187			Part::First => f.write_str("[0]"),
188			Part::Start(v) => write!(f, "{v}"),
189			Part::Field(v) => write!(f, ".{v}"),
190			Part::Flatten => f.write_str("…"),
191			Part::Index(v) => write!(f, "[{v}]"),
192			Part::Where(v) => write!(f, "[WHERE {v}]"),
193			Part::Graph(v) => write!(f, "{v}"),
194			Part::Value(v) => write!(f, "[{v}]"),
195			Part::Method(v, a) => write!(f, ".{v}({})", Fmt::comma_separated(a)),
196			Part::Destructure(v) => {
197				f.write_str(".{")?;
198				if !is_pretty() {
199					f.write_char(' ')?;
200				}
201				if !v.is_empty() {
202					let indent = pretty_indent();
203					write!(f, "{}", Fmt::pretty_comma_separated(v))?;
204					drop(indent);
205				}
206				if is_pretty() {
207					f.write_char('}')
208				} else {
209					f.write_str(" }")
210				}
211			}
212			Part::Optional => write!(f, "?"),
213			Part::Recurse(v, nest, instruction) => {
214				write!(f, ".{{{v}")?;
215				if let Some(instruction) = instruction {
216					write!(f, "+{instruction}")?;
217				}
218				write!(f, "}}")?;
219
220				if let Some(nest) = nest {
221					write!(f, "({nest})")?;
222				}
223
224				Ok(())
225			}
226			Part::Doc => write!(f, "@"),
227			Part::RepeatRecurse => write!(f, ".@"),
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: Ident,
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
251impl<'a> RecursionPlan {
252	pub async fn compute(
253		&self,
254		stk: &mut Stk,
255		ctx: &Context,
256		opt: &Options,
257		doc: Option<&CursorDoc>,
258		rec: Recursion<'a>,
259	) -> Result<Value, Error> {
260		match rec.current {
261			Value::Array(value) => stk
262				.scope(|scope| {
263					let futs = value.iter().map(|value| {
264						scope.run(|stk| {
265							let rec = rec.with_current(value);
266							self.compute_inner(stk, ctx, opt, doc, rec)
267						})
268					});
269					try_join_all_buffered(futs)
270				})
271				.await
272				.map(Into::into),
273			_ => stk.run(|stk| self.compute_inner(stk, ctx, opt, doc, rec)).await,
274		}
275	}
276
277	pub async fn compute_inner(
278		&self,
279		stk: &mut Stk,
280		ctx: &Context,
281		opt: &Options,
282		doc: Option<&CursorDoc>,
283		rec: Recursion<'a>,
284	) -> Result<Value, Error> {
285		match self {
286			Self::Repeat => compute_idiom_recursion(stk, ctx, opt, doc, rec).await,
287			Self::Destructure {
288				parts,
289				field,
290				before,
291				plan,
292				after,
293			} => {
294				let v = stk.run(|stk| rec.current.get(stk, ctx, opt, doc, before)).await?;
295				let v = plan.compute(stk, ctx, opt, doc, rec.with_current(&v)).await?;
296				let v = stk.run(|stk| v.get(stk, ctx, opt, doc, after)).await?;
297				let v = clean_iteration(v);
298
299				if rec.iterated < rec.min && is_final(&v) {
300					// We do not use get_final here, because it's not a result
301					// the user will see, it's rather about path elimination
302					// By returning NONE, an array to be eliminated will be
303					// filled with NONE, and thus eliminated
304					return Ok(Value::None);
305				}
306
307				let path = &[Part::Destructure(parts.to_owned())];
308				match stk.run(|stk| rec.current.get(stk, ctx, opt, doc, path)).await? {
309					Value::Object(mut obj) => {
310						obj.insert(field.to_raw(), v);
311						Ok(Value::Object(obj))
312					}
313					Value::None => Ok(Value::None),
314					v => Err(Error::Unreachable(format!(
315						"Expected an object or none, found {}.",
316						v.kindof()
317					))),
318				}
319			}
320		}
321	}
322}
323
324pub trait FindRecursionPlan<'a> {
325	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])>;
326}
327
328impl<'a> FindRecursionPlan<'a> for &'a [Part] {
329	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])> {
330		for (i, p) in self.iter().enumerate() {
331			if let Some(plan) = p.recursion_plan() {
332				return Some((&self[..i], plan, &self[(i + 1)..]));
333			}
334		}
335
336		None
337	}
338}
339
340impl<'a> FindRecursionPlan<'a> for &'a Idiom {
341	fn find_recursion_plan(&'a self) -> Option<(&'a [Part], RecursionPlan, &'a [Part])> {
342		for (i, p) in self.iter().enumerate() {
343			if let Some(plan) = p.recursion_plan() {
344				return Some((&self[..i], plan, &self[(i + 1)..]));
345			}
346		}
347
348		None
349	}
350}
351
352// ------------------------------
353
354pub trait SplitByRepeatRecurse<'a> {
355	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])>;
356}
357
358impl<'a> SplitByRepeatRecurse<'a> for &'a [Part] {
359	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])> {
360		self.iter()
361			.position(|p| matches!(p, Part::RepeatRecurse))
362			// We exclude the `@` repeat recurse symbol here, because
363			// it ensures we will loop the idiom path, instead of using
364			// `.get()` to recurse
365			.map(|i| (&self[..i], &self[(i + 1)..]))
366	}
367}
368
369impl<'a> SplitByRepeatRecurse<'a> for &'a Idiom {
370	fn split_by_repeat_recurse(&'a self) -> Option<(&'a [Part], &'a [Part])> {
371		self.iter()
372			.position(|p| matches!(p, Part::RepeatRecurse))
373			// We exclude the `@` repeat recurse symbol here, because
374			// it ensures we will loop the idiom path, instead of using
375			// `.get()` to recurse
376			.map(|i| (&self[..i], &self[(i + 1)..]))
377	}
378}
379
380// ------------------------------
381
382pub trait Next<'a> {
383	fn next(&'a self) -> &'a [Part];
384}
385
386impl<'a> Next<'a> for &'a [Part] {
387	fn next(&'a self) -> &'a [Part] {
388		match self.len() {
389			0 => &[],
390			_ => &self[1..],
391		}
392	}
393}
394
395// ------------------------------
396
397pub trait Skip<'a> {
398	fn skip(&'a self, amount: usize) -> &'a [Part];
399}
400
401impl<'a> Skip<'a> for &'a [Part] {
402	fn skip(&'a self, amount: usize) -> &'a [Part] {
403		match self.len() {
404			0 => &[],
405			_ => &self[amount..],
406		}
407	}
408}
409
410// ------------------------------
411
412pub trait NextMethod<'a> {
413	fn next_method(&'a self) -> &'a [Part];
414}
415
416impl<'a> NextMethod<'a> for &'a [Part] {
417	fn next_method(&'a self) -> &'a [Part] {
418		match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
419			None => &[],
420			Some(i) => &self[i..],
421		}
422	}
423}
424
425impl<'a> NextMethod<'a> for &'a Idiom {
426	fn next_method(&'a self) -> &'a [Part] {
427		match self.iter().position(|p| matches!(p, Part::Method(_, _))) {
428			None => &[],
429			Some(i) => &self[i..],
430		}
431	}
432}
433
434// ------------------------------
435
436#[revisioned(revision = 1)]
437#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
438#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
439#[non_exhaustive]
440pub enum DestructurePart {
441	All(Ident),
442	Field(Ident),
443	Aliased(Ident, Idiom),
444	Destructure(Ident, Vec<DestructurePart>),
445}
446
447impl DestructurePart {
448	pub fn field(&self) -> &Ident {
449		match self {
450			DestructurePart::All(v) => v,
451			DestructurePart::Field(v) => v,
452			DestructurePart::Aliased(v, _) => v,
453			DestructurePart::Destructure(v, _) => v,
454		}
455	}
456
457	pub fn path(&self) -> Vec<Part> {
458		match self {
459			DestructurePart::All(v) => vec![Part::Field(v.clone()), Part::All],
460			DestructurePart::Field(v) => vec![Part::Field(v.clone())],
461			DestructurePart::Aliased(_, v) => v.0.clone(),
462			DestructurePart::Destructure(f, d) => {
463				vec![Part::Field(f.clone()), Part::Destructure(d.clone())]
464			}
465		}
466	}
467}
468
469impl fmt::Display for DestructurePart {
470	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
471		match self {
472			DestructurePart::All(fd) => write!(f, "{fd}.*"),
473			DestructurePart::Field(fd) => write!(f, "{fd}"),
474			DestructurePart::Aliased(fd, v) => write!(f, "{fd}: {v}"),
475			DestructurePart::Destructure(fd, d) => {
476				write!(f, "{fd}{}", Part::Destructure(d.clone()))
477			}
478		}
479	}
480}
481
482// ------------------------------
483
484#[revisioned(revision = 1)]
485#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
486#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
487#[non_exhaustive]
488pub enum Recurse {
489	Fixed(u32),
490	Range(Option<u32>, Option<u32>),
491}
492
493impl TryInto<(u32, Option<u32>)> for Recurse {
494	type Error = Error;
495	fn try_into(self) -> Result<(u32, Option<u32>), Error> {
496		let v = match self {
497			Recurse::Fixed(v) => (v, Some(v)),
498			Recurse::Range(min, max) => {
499				let min = min.unwrap_or(1);
500				(min, max)
501			}
502		};
503
504		match v {
505			(min, _) if min < 1 => Err(Error::InvalidBound {
506				found: min.to_string(),
507				expected: "at least 1".into(),
508			}),
509			(_, Some(max)) if max > (*IDIOM_RECURSION_LIMIT as u32) => Err(Error::InvalidBound {
510				found: max.to_string(),
511				expected: format!("{} at most", *IDIOM_RECURSION_LIMIT),
512			}),
513			v => Ok(v),
514		}
515	}
516}
517
518impl fmt::Display for Recurse {
519	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520		match self {
521			Recurse::Fixed(v) => write!(f, "{v}"),
522			Recurse::Range(beg, end) => match (beg, end) {
523				(None, None) => write!(f, ".."),
524				(Some(beg), None) => write!(f, "{beg}.."),
525				(None, Some(end)) => write!(f, "..{end}"),
526				(Some(beg), Some(end)) => write!(f, "{beg}..{end}"),
527			},
528		}
529	}
530}
531
532// ------------------------------
533
534#[revisioned(revision = 1)]
535#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
536#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
537#[non_exhaustive]
538pub enum RecurseInstruction {
539	Path {
540		// Do we include the starting point in the paths?
541		inclusive: bool,
542	},
543	Collect {
544		// Do we include the starting point in the collection?
545		inclusive: bool,
546	},
547	Shortest {
548		// What ending node are we looking for?
549		expects: Value,
550		// Do we include the starting point in the collection?
551		inclusive: bool,
552	},
553}
554
555macro_rules! to_vec_value {
556	(&$v: expr) => {
557		match $v {
558			Value::Array(v) => &v.0,
559			v => &vec![v.to_owned()],
560		}
561	};
562	($v: expr) => {
563		match $v {
564			Value::Array(v) => v.0,
565			v => vec![v],
566		}
567	};
568}
569
570macro_rules! walk_paths {
571	(
572		$stk: ident,
573		$ctx: ident,
574		$opt: ident,
575		$doc: ident,
576		$rec: ident,
577		$finished: ident,
578		$inclusive: ident,
579		$expects: expr
580	) => {{
581		// Collection of paths we will continue processing
582		// in the next iteration
583		let mut open: Vec<Value> = vec![];
584
585		// Obtain an array value to iterate over
586		let paths = to_vec_value!(&$rec.current);
587
588		// Process all paths
589		for path in paths.iter() {
590			// Obtain an array value to iterate over
591			let path = to_vec_value!(&path);
592
593			// We always operate on the last value in the path
594			// If the path is empty, we skip it
595			let Some(last) = path.last() else {
596				continue;
597			};
598
599			// Apply the recursed path to the last value
600			let res = $stk.run(|stk| last.get(stk, $ctx, $opt, $doc, $rec.path)).await?;
601
602			// If we encounter a final value, we add it to the finished collection.
603			// - If expects is some, we are seeking for the shortest path, in which
604			//   case we eliminate the path.
605			// - In case this is the first iteration, and paths are not inclusive of
606			//   the starting point, we eliminate the it.
607			// - If we have not yet reached minimum depth, the path is eliminated aswell.
608			if is_final(&res) || &res == last {
609				if $expects.is_none()
610					&& ($rec.iterated > 1 || *$inclusive)
611					&& $rec.iterated >= $rec.min
612				{
613					$finished.push(path.to_owned().into());
614				}
615
616				continue;
617			}
618
619			// Obtain an array value to iterate over
620			let steps = to_vec_value!(res);
621
622			// Did we reach the final iteration?
623			let reached_max = $rec.max.is_some_and(|max| $rec.iterated >= max);
624
625			// For every step, prefix it with the current path
626			for step in steps.iter() {
627				// If this is the first iteration, and in case we are not inclusive
628				// of the starting point, we only add the step to the open collection
629				let val = if $rec.iterated == 1 && !*$inclusive {
630					Value::from(vec![step.to_owned()])
631				} else {
632					let mut path = path.to_owned();
633					path.push(step.to_owned());
634					Value::from(path)
635				};
636
637				// If we expect a certain value, let's check if we have reached it
638				// If so, we iterate over the steps and assign them to the finished collection
639				// We then return Value::None, indicating to the recursion loop that we are done
640				if let Some(expects) = $expects {
641					if step == expects {
642						let steps = to_vec_value!(val);
643
644						for step in steps {
645							$finished.push(step);
646						}
647
648						return Ok(Value::None);
649					}
650				}
651
652				// If we have reached the maximum amount of iterations, and are collecting
653				// individual paths, we assign them to the finished collection
654				if reached_max {
655					if $expects.is_none() {
656						$finished.push(val);
657					}
658				} else {
659					open.push(val);
660				}
661			}
662		}
663
664		Ok(open.into())
665	}};
666}
667
668impl RecurseInstruction {
669	pub(crate) async fn compute(
670		&self,
671		stk: &mut Stk,
672		ctx: &Context,
673		opt: &Options,
674		doc: Option<&CursorDoc>,
675		rec: Recursion<'_>,
676		finished: &mut Vec<Value>,
677	) -> Result<Value, Error> {
678		match self {
679			Self::Path {
680				inclusive,
681			} => {
682				walk_paths!(stk, ctx, opt, doc, rec, finished, inclusive, Option::<&Value>::None)
683			}
684			Self::Shortest {
685				expects,
686				inclusive,
687			} => {
688				let expects =
689					Value::from(expects.compute(stk, ctx, opt, doc).await?.coerce_to_record()?);
690				walk_paths!(stk, ctx, opt, doc, rec, finished, inclusive, Some(&expects))
691			}
692			Self::Collect {
693				inclusive,
694			} => {
695				macro_rules! persist {
696					($finished:ident, $subject:expr) => {
697						match $subject {
698							Value::Array(v) => {
699								for v in v.iter() {
700									// Add a unique value to the finished collection
701									if !$finished.contains(v) {
702										$finished.push(v.to_owned());
703									}
704								}
705							}
706							v => {
707								if !$finished.contains(v) {
708									// Add a unique value to the finished collection
709									$finished.push(v.to_owned());
710								}
711							}
712						}
713					};
714				}
715
716				// If we are inclusive, we add the starting point to the collection
717				if rec.iterated == 1 && *inclusive {
718					persist!(finished, rec.current);
719				}
720
721				// Apply the recursed path to the current values
722				let res = stk.run(|stk| rec.current.get(stk, ctx, opt, doc, rec.path)).await?;
723				// Clean the iteration
724				let res = clean_iteration(res);
725
726				// Persist any new values from the result
727				persist!(finished, &res);
728
729				// Continue
730				Ok(res)
731			}
732		}
733	}
734}
735
736impl fmt::Display for RecurseInstruction {
737	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
738		match self {
739			Self::Path {
740				inclusive,
741			} => {
742				write!(f, "path")?;
743
744				if *inclusive {
745					write!(f, "+inclusive")?;
746				}
747
748				Ok(())
749			}
750			Self::Collect {
751				inclusive,
752			} => {
753				write!(f, "collect")?;
754
755				if *inclusive {
756					write!(f, "+inclusive")?;
757				}
758
759				Ok(())
760			}
761			Self::Shortest {
762				expects,
763				inclusive,
764			} => {
765				write!(f, "shortest={expects}")?;
766
767				if *inclusive {
768					write!(f, "+inclusive")?;
769				}
770
771				Ok(())
772			}
773		}
774	}
775}