surrealdb-core 3.3.1

A scalable, distributed, collaborative, document-graph database, for the realtime web
use anyhow::Result;
use futures::future::try_join_all;
use reblessive::tree::Stk;

use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::exe::FlowResultExt as _;
use crate::expr::field::{Field, Fields};
use crate::expr::part::Next;
use crate::expr::statements::select::SelectStatement;
use crate::expr::{Expr, Idiom, Literal, Lookup, Part};
use crate::val::Value;

pub(crate) async fn value_fetch(
	this_: &mut Value,
	stk: &mut Stk,
	ctx: &FrozenContext,
	opt: &Options,
	path: &[Part],
) -> Result<()> {
	let mut this = this_;
	let mut iter = path.iter();
	let mut prev = path;

	// Loop over the path.
	// If the we just need to select a sub section of a value we update this to
	// point to the new subsection of the value. Otherwise we call into fetch
	// again and then immediately return.
	// If we encounter a idiom application which does not make sense, like `(1).foo`
	// just return Ok(())
	while let Some(p) = iter.next() {
		match p {
			Part::Lookup(g) => match this {
				Value::Object(o) => {
					let Some(v) = o.rid() else {
						return Ok(());
					};

					let mut v = Value::RecordId(v);
					return stk
						.run(|stk| {
							crate::legacy::value_fetch(&mut v, stk, ctx, opt, iter.as_slice())
						})
						.await;
				}
				Value::RecordId(x) => {
					let what = Expr::Idiom(Idiom(vec![
						Part::Start(Expr::Literal(Literal::RecordId(x.clone().into_literal()))),
						Part::Lookup(Box::new(Lookup {
							what: g.what.clone(),
							kind: g.kind.clone(),
							..Default::default()
						})),
					]));

					let stm = SelectStatement {
						fields: g.expr.clone().unwrap_or(Fields::all()),
						what: vec![what],
						cond: g.cond.clone(),
						limit: g.limit.clone(),
						order: g.order.clone(),
						split: g.split.clone(),
						group: g.group.clone(),
						start: g.start.clone(),
						omit: vec![],
						only: false,
						with: None,
						fetch: None,
						version: Expr::Literal(Literal::None),
						timeout: Expr::Literal(Literal::None),
						explain: None,
						tempfiles: false,
						for_update: false,
					};
					*this = crate::legacy::value_get(
						&crate::legacy::select_statement_compute(&stm, stk, ctx, opt, None)
							.await?
							.all(),
						stk,
						ctx,
						opt,
						None,
						path.next(),
					)
					.await
					.catch_return()?
					.flatten();
					return Ok(());
				}
				Value::Array(x) => {
					// apply this path to every entry of the array.
					stk.scope(|scope| {
						let futs = x.iter_mut().map(|v| {
							scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, prev))
						});
						try_join_all(futs)
					})
					.await?;
					return Ok(());
				}
				// break her to be comp
				_ => return Ok(()),
			},
			Part::Field(f) => match this {
				Value::Object(o) => {
					let Some(x) = o.get_mut(f.as_str()) else {
						return Ok(());
					};
					this = x;
				}
				Value::Array(x) => {
					// apply this path to every entry of the array.
					stk.scope(|scope| {
						let futs = x.iter_mut().map(|v| {
							scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, prev))
						});
						try_join_all(futs)
					})
					.await?;
					return Ok(());
				}
				_ => break,
			},
			Part::Value(v) => {
				let v = stk
					.run(|stk| crate::legacy::expr_compute(v, stk, ctx, opt, None))
					.await
					.catch_return()?;
				match this {
					Value::Object(obj) => {
						let Some(x) = obj.get_mut(v.coerce_to::<String>()?.as_str()) else {
							return Ok(());
						};
						this = x;
					}
					Value::Array(array) => {
						if let Value::Range(x) = v {
							let Some(range) = x.coerce_to_typed::<i64>()?.slice(array) else {
								return Ok(());
							};
							let mut range = Value::Array(range.to_vec().into());
							return stk
								.run(|stk| {
									crate::legacy::value_fetch(
										&mut range,
										stk,
										ctx,
										opt,
										iter.as_slice(),
									)
								})
								.await;
						}
						let idx = v.coerce_to::<i64>()?;
						// A negative index does not address any element; skip silently to
						// match the behaviour of `arr[-1]` returning NONE in expressions.
						let Ok(idx) = usize::try_from(idx) else {
							return Ok(());
						};
						let Some(x) = array.get_mut(idx) else {
							return Ok(());
						};
						this = x;
					}
					_ => return Ok(()),
				}
			}
			Part::Destructure(p) => match this {
				Value::Array(x) => {
					// apply this path to every entry of the array.
					stk.scope(|scope| {
						let futs = x.iter_mut().map(|v| {
							scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, prev))
						});
						try_join_all(futs)
					})
					.await?;
				}
				Value::Object(_) => {
					for p in p.iter() {
						let mut destructure_path = p.path();
						destructure_path.extend_from_slice(path);
						stk.run(|stk| {
							crate::legacy::value_fetch(this, stk, ctx, opt, &destructure_path)
						})
						.await?;
					}
					return Ok(());
				}
				_ => return Ok(()),
			},
			Part::All => match this {
				Value::Object(x) => {
					let next_path = iter.as_slice();
					// no need to spawn all those futures if their is no more paths to
					// calculate
					if next_path.is_empty() {
						break;
					}

					stk.scope(|scope| {
						let futs = x.iter_mut().map(|(_, v)| {
							scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, next_path))
						});
						try_join_all(futs)
					})
					.await?;
					return Ok(());
				}
				Value::Array(x) => {
					let next_path = iter.as_slice();
					// no need to spawn all those futures if their is no more paths to
					// calculate
					if next_path.is_empty() {
						break;
					}

					stk.scope(|scope| {
						let futs = x.iter_mut().map(|v| {
							scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, next_path))
						});
						try_join_all(futs)
					})
					.await?;
					return Ok(());
				}
				_ => break,
			},
			Part::First => match this {
				Value::Array(x) => {
					let Some(x) = x.first_mut() else {
						return Ok(());
					};
					this = x;
				}
				_ => return Ok(()),
			},
			Part::Last => match this {
				Value::Array(x) => {
					let Some(x) = x.last_mut() else {
						return Ok(());
					};
					this = x;
				}
				_ => return Ok(()),
			},
			Part::Where(w) => match this {
				Value::Array(x) => {
					for v in x.iter_mut() {
						let doc = v.clone().into();
						if stk
							.run(|stk| crate::legacy::expr_compute(w, stk, ctx, opt, Some(&doc)))
							.await
							.catch_return()?
							.is_truthy()
						{
							stk.run(|stk| {
								crate::legacy::value_fetch(v, stk, ctx, opt, iter.as_slice())
							})
							.await?;
						}
					}
				}
				_ => return Ok(()),
			},
			_ => break,
		}
		prev = iter.as_slice();
	}

	// If the final value is on of following types we still need to compute it.
	match this {
		Value::Array(v) => {
			stk.scope(|scope| {
				let futs = v
					.iter_mut()
					.map(|v| scope.run(|stk| crate::legacy::value_fetch(v, stk, ctx, opt, path)));
				try_join_all(futs)
			})
			.await?;
			Ok(())
		}
		Value::RecordId(v) => {
			// Clone the record
			let val = v.clone();
			// Fetch the remote embedded record
			let stm = SelectStatement {
				fields: Fields::Select(vec![Field::All]),
				what: vec![Expr::Literal(Literal::RecordId(val.into_literal()))],
				omit: vec![],
				only: false,
				with: None,
				cond: None,
				split: None,
				group: None,
				order: None,
				limit: None,
				start: None,
				fetch: None,
				version: Expr::Literal(Literal::None),
				timeout: Expr::Literal(Literal::None),
				explain: None,
				tempfiles: false,
				for_update: false,
			};
			*this =
				crate::legacy::select_statement_compute(&stm, stk, ctx, opt, None).await?.first();
			Ok(())
		}
		_ => Ok(()),
	}
}