use std::sync::Arc;
use futures::StreamExt;
use surrealdb_types::{SqlFormat, ToSql};
pub(crate) use crate::exec::operators::recursion::evaluate_repeat_recurse;
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{AccessMode, BoxFut, ContextLevel, ExecOperator};
use crate::expr::FlowResult;
use crate::val::Value;
#[derive(Debug, Clone)]
pub enum PhysicalRecurseInstruction {
Default,
Collect,
Path,
Shortest {
target: Arc<dyn PhysicalExpr>,
},
}
#[derive(Debug, Clone)]
pub struct RecursePart {
pub op: Arc<dyn ExecOperator>,
}
impl PhysicalExpr for RecursePart {
fn name(&self) -> &'static str {
"Recurse"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn required_context(&self) -> ContextLevel {
self.op.required_context()
}
fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>> {
Box::pin(async move {
let value = ctx.current_value.cloned().unwrap_or(Value::None);
let bound_ctx = ctx.exec_ctx.with_current_value(value);
let bound_ctx = if ctx.skip_fetch_perms {
bound_ctx.with_skip_fetch_perms(true)
} else {
bound_ctx
};
let stream = self.op.execute(&bound_ctx).map_err(|e| match e {
crate::expr::ControlFlow::Err(e) => crate::expr::ControlFlow::Err(e),
other => other,
})?;
futures::pin_mut!(stream);
let mut result = Value::None;
while let Some(batch_result) = stream.next().await {
let batch = batch_result?;
if let Some(v) = batch.values.into_iter().next() {
result = v;
}
}
Ok(result)
})
}
fn access_mode(&self) -> AccessMode {
self.op.access_mode()
}
fn embedded_operators(&self) -> Vec<(&str, &Arc<dyn ExecOperator>)> {
vec![("recurse", &self.op)]
}
}
impl ToSql for RecursePart {
fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
let attrs = self.op.attrs();
f.push_str(".{");
for (k, v) in &attrs {
if k == "depth" {
if v.contains("..") {
let parts: Vec<&str> = v.split("..").collect();
if parts.len() == 2 {
let min = parts[0];
let max = parts[1];
if min != "1" {
f.push_str(min);
}
f.push_str("..");
f.push_str(max);
}
} else {
f.push_str(v);
}
}
}
for (k, v) in &attrs {
if k == "instruction" {
match v.as_str() {
"default" => {}
"collect" => f.push_str("+collect"),
"path" => f.push_str("+path"),
"shortest" => f.push_str("+shortest=..."),
_ => {}
}
}
}
f.push('}');
}
}
#[derive(Debug, Clone)]
pub struct RepeatRecursePart;
impl PhysicalExpr for RepeatRecursePart {
fn name(&self) -> &'static str {
"RepeatRecurse"
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn required_context(&self) -> ContextLevel {
ContextLevel::Root
}
fn evaluate<'a>(&'a self, ctx: EvalContext<'a>) -> BoxFut<'a, FlowResult<Value>> {
Box::pin(async move {
let value = ctx.current_value.unwrap_or(&Value::NONE);
evaluate_repeat_recurse(value, ctx).await
})
}
fn access_mode(&self) -> AccessMode {
AccessMode::ReadOnly
}
}
impl ToSql for RepeatRecursePart {
fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
f.push('@');
}
}
pub(crate) fn value_hash(value: &Value) -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
match value {
Value::RecordId(rid) => {
0u8.hash(&mut hasher);
rid.hash(&mut hasher);
}
Value::None => {
1u8.hash(&mut hasher);
}
Value::Null => {
2u8.hash(&mut hasher);
}
Value::Bool(b) => {
3u8.hash(&mut hasher);
b.hash(&mut hasher);
}
Value::String(s) => {
4u8.hash(&mut hasher);
s.hash(&mut hasher);
}
Value::Number(n) => {
5u8.hash(&mut hasher);
n.to_string().hash(&mut hasher);
}
Value::Uuid(u) => {
6u8.hash(&mut hasher);
u.0.hash(&mut hasher);
}
Value::Array(arr) => {
7u8.hash(&mut hasher);
arr.len().hash(&mut hasher);
for (i, v) in arr.iter().enumerate() {
if i >= 8 {
break;
}
value_hash(v).hash(&mut hasher);
}
}
Value::Object(obj) => {
8u8.hash(&mut hasher);
obj.len().hash(&mut hasher);
for (i, (k, v)) in obj.iter().enumerate() {
if i >= 8 {
break;
}
k.hash(&mut hasher);
value_hash(v).hash(&mut hasher);
}
}
_ => {
255u8.hash(&mut hasher);
format!("{:?}", value).hash(&mut hasher);
}
}
hasher.finish()
}