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