use std::sync::Arc;
use surrealdb_types::ToSql;
use super::common::{RecursionBounds, eval_buffered, is_recursion_target};
use crate::exec::FlowResult;
use crate::exec::parts::{evaluate_physical_path, is_final};
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::val::Value;
pub(crate) async fn evaluate_recurse_path(
start: &Value,
path: &[Arc<dyn PhysicalExpr>],
bounds: RecursionBounds,
inclusive: bool,
ctx: EvalContext<'_>,
) -> FlowResult<Value> {
let min_depth = bounds.min;
let max_depth = bounds.cap();
let mut completed_paths: Vec<Value> = Vec::new();
let mut active_paths: Vec<Vec<Value>> = if inclusive {
vec![vec![start.clone()]]
} else {
vec![vec![]]
};
let mut depth = 0u32;
while depth < max_depth && !active_paths.is_empty() {
let mut next_paths = Vec::new();
let futures: Vec<_> = active_paths
.iter()
.map(|current_path| {
let current_value = current_path.last().unwrap_or(start);
evaluate_physical_path(current_value, path, ctx.with_value(current_value))
})
.collect();
let eval_results = eval_buffered(futures).await?;
for (mut current_path, result) in active_paths.into_iter().zip(eval_results) {
let values = match result {
Value::Array(arr) => arr.0,
Value::None | Value::Null => {
if depth >= min_depth && !current_path.is_empty() {
completed_paths.push(Value::Array(current_path.into()));
}
continue;
}
other => vec![other],
};
let mut valid_targets = Vec::new();
for v in values {
if is_final(&v) {
continue;
}
if !is_recursion_target(&v) {
return Err(crate::err::Error::InvalidRecursionTarget {
value: v.to_sql(),
}
.into());
}
valid_targets.push(v);
}
if valid_targets.is_empty() {
if depth >= min_depth && !current_path.is_empty() {
completed_paths.push(Value::Array(current_path.into()));
}
} else {
let mut iter = valid_targets.into_iter().peekable();
while let Some(v) = iter.next() {
if iter.peek().is_some() {
let mut new_path = current_path.clone();
new_path.push(v);
next_paths.push(new_path);
} else {
current_path.push(v);
next_paths.push(current_path);
break;
}
}
}
}
active_paths = next_paths;
depth += 1;
}
if bounds.errors_on_limit() && !active_paths.is_empty() {
return Err(crate::err::Error::IdiomRecursionLimitExceeded {
limit: bounds.system_limit,
}
.into());
}
for p in active_paths {
if !p.is_empty() && depth >= min_depth {
completed_paths.push(Value::Array(p.into()));
}
}
Ok(Value::Array(completed_paths.into()))
}