surrealdb-core-nightly 2.1.20250210

A nightly release of the surrealdb-core crate
Documentation
use crate::ctx::Context;
use crate::dbs::result::Results;
use crate::dbs::{Iterable, Statement};
use crate::idx::planner::RecordStrategy;
use crate::sql::{Object, Value};
use std::collections::HashMap;

pub(super) struct Plan {
	pub(super) do_iterate: bool,
	pub(super) explanation: Option<Explanation>,
}

impl Plan {
	pub(super) fn new(
		ctx: &Context,
		stm: &Statement<'_>,
		iterables: &Vec<Iterable>,
		results: &Results,
	) -> Self {
		let (do_iterate, explanation) = match stm.explain() {
			None => (true, None),
			Some(e) => {
				let mut exp = Explanation::default();
				for i in iterables {
					exp.add_iter(ctx, i);
				}
				if let Some(qp) = ctx.get_query_planner() {
					for reason in qp.fallbacks() {
						exp.add_fallback(reason.to_string());
					}
				}
				results.explain(&mut exp);
				(e.0, Some(exp))
			}
		};
		Self {
			do_iterate,
			explanation,
		}
	}
}

#[derive(Default)]
pub(super) struct Explanation(Vec<ExplainItem>);

impl Explanation {
	fn add_iter(&mut self, ctx: &Context, iter: &Iterable) {
		self.0.push(ExplainItem::new_iter(ctx, iter));
	}

	pub(super) fn add_fetch(&mut self, count: usize) {
		self.0.push(ExplainItem::new_fetch(count));
	}

	pub(super) fn add_collector(
		&mut self,
		collector_type: &str,
		details: Vec<(&'static str, Value)>,
	) {
		self.0.push(ExplainItem::new_collector(collector_type, details));
	}
	fn add_fallback(&mut self, reason: String) {
		self.0.push(ExplainItem::new_fallback(reason));
	}

	pub(super) fn add_record_strategy(&mut self, rs: RecordStrategy) {
		self.0.push(ExplainItem::new_record_strategy(rs));
	}

	pub(super) fn add_start_limit(
		&mut self,
		start_skip: Option<usize>,
		cancel_on_limit: Option<u32>,
	) {
		self.0.push(ExplainItem::new_start_limit(start_skip, cancel_on_limit));
	}
	pub(super) fn output(self) -> Vec<Value> {
		self.0.into_iter().map(|e| e.into()).collect()
	}
}

struct ExplainItem {
	name: Value,
	details: Vec<(&'static str, Value)>,
}

impl ExplainItem {
	fn new_fetch(count: usize) -> Self {
		Self {
			name: "Fetch".into(),
			details: vec![("count", count.into())],
		}
	}

	fn new_fallback(reason: String) -> Self {
		Self {
			name: "Fallback".into(),
			details: vec![("reason", reason.into())],
		}
	}

	fn new_iter(ctx: &Context, iter: &Iterable) -> Self {
		match iter {
			Iterable::Value(v) => Self {
				name: "Iterate Value".into(),
				details: vec![("value", v.to_owned())],
			},
			Iterable::Yield(t) => Self {
				name: "Iterate Yield".into(),
				details: vec![("table", Value::from(t.0.to_owned()))],
			},
			Iterable::Thing(t) => Self {
				name: "Iterate Thing".into(),
				details: vec![("thing", Value::Thing(t.to_owned()))],
			},
			Iterable::Defer(t) => Self {
				name: "Iterate Defer".into(),
				details: vec![("thing", Value::Thing(t.to_owned()))],
			},
			Iterable::Edges(e) => Self {
				name: "Iterate Edges".into(),
				details: vec![("from", Value::Thing(e.from.to_owned()))],
			},
			Iterable::Table(t, rs) => Self {
				name: match rs {
					RecordStrategy::Count => "Iterate Table Count",
					RecordStrategy::KeysOnly => "Iterate Table Keys",
					RecordStrategy::KeysAndValues => "Iterate Table",
				}
				.into(),
				details: vec![("table", Value::from(t.0.to_owned()))],
			},
			Iterable::Range(tb, r, rs) => Self {
				name: match rs {
					RecordStrategy::Count => "Iterate Range Count",
					RecordStrategy::KeysOnly => "Iterate Range Keys",
					RecordStrategy::KeysAndValues => "Iterate Range",
				}
				.into(),
				details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
			},
			Iterable::Mergeable(t, v) => Self {
				name: "Iterate Mergeable".into(),
				details: vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())],
			},
			Iterable::Relatable(t1, t2, t3, None) => Self {
				name: "Iterate Relatable".into(),
				details: vec![
					("thing-1", Value::Thing(t1.to_owned())),
					("thing-2", Value::Thing(t2.to_owned())),
					("thing-3", Value::Thing(t3.to_owned())),
				],
			},
			Iterable::Relatable(t1, t2, t3, Some(v)) => Self {
				name: "Iterate Relatable".into(),
				details: vec![
					("thing-1", Value::Thing(t1.to_owned())),
					("thing-2", Value::Thing(t2.to_owned())),
					("thing-3", Value::Thing(t3.to_owned())),
					("value", v.to_owned()),
				],
			},
			Iterable::Index(t, ir, rs) => {
				let mut details = vec![("table", Value::from(t.0.to_owned()))];
				if let Some(qp) = ctx.get_query_planner() {
					if let Some(exe) = qp.get_query_executor(&t.0) {
						details.push(("plan", exe.explain(*ir)));
					}
				}
				Self {
					name: match rs {
						RecordStrategy::Count => "Iterate Index Count",
						RecordStrategy::KeysOnly => "Iterate Index Keys",
						RecordStrategy::KeysAndValues => "Iterate Index",
					}
					.into(),
					details,
				}
			}
		}
	}

	pub(super) fn new_collector(
		collector_type: &str,
		mut details: Vec<(&'static str, Value)>,
	) -> Self {
		details.insert(0, ("type", collector_type.into()));
		Self {
			name: "Collector".into(),
			details,
		}
	}
	pub(super) fn new_record_strategy(rs: RecordStrategy) -> Self {
		Self {
			name: "RecordStrategy".into(),
			details: vec![(
				"type",
				match rs {
					RecordStrategy::Count => "Count",
					RecordStrategy::KeysOnly => "KeysOnly",
					RecordStrategy::KeysAndValues => "KeysAndValues",
				}
				.into(),
			)],
		}
	}

	pub(super) fn new_start_limit(start_skip: Option<usize>, cancel_on_limit: Option<u32>) -> Self {
		let mut details = vec![];
		if let Some(s) = start_skip {
			details.push(("SkipStart", s.into()));
		}
		if let Some(l) = cancel_on_limit {
			details.push(("CancelOnLimit", l.into()));
		}
		Self {
			name: "StartLimitStrategy".into(),
			details,
		}
	}
}

impl From<ExplainItem> for Value {
	fn from(i: ExplainItem) -> Self {
		let explain = Object::from(HashMap::from([
			("operation", i.name),
			("detail", Value::Object(Object::from(HashMap::from_iter(i.details)))),
		]));
		Value::from(explain)
	}
}