use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use surrealdb_types::ToSql;
use super::common::{
self, RecursionBounds, discover_body_targets, eval_buffered_all, is_recursion_target,
};
use crate::exec::parts::recurse::value_hash;
use crate::exec::parts::{clean_iteration, evaluate_physical_path, get_final, is_final};
use crate::exec::physical_expr::{EvalContext, PhysicalExpr, RecursionCtx};
use crate::exec::{ExecutionContext, FlowResult};
use crate::expr::ControlFlow;
use crate::val::Value;
#[derive(Debug)]
pub(crate) struct PathEliminationSignal;
impl std::fmt::Display for PathEliminationSignal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "path elimination signal")
}
}
impl std::error::Error for PathEliminationSignal {}
pub(crate) fn evaluate_repeat_recurse<'a>(
value: &'a Value,
ctx: EvalContext<'a>,
) -> crate::exec::BoxFut<'a, FlowResult<Value>> {
Box::pin(async move {
let rec_ctx = match &ctx.recursion_ctx {
Some(rc) => rc.clone(),
None => {
return Err(crate::err::Error::UnsupportedRepeatRecurse.into());
}
};
if let Some(ref sink) = rec_ctx.discovery_sink {
let values_to_write: Vec<Value> = match value {
Value::Array(arr) => {
let mut targets = Vec::new();
for v in arr.iter() {
if is_final(v) {
continue;
}
if !is_recursion_target(v) {
return Err(crate::err::Error::InvalidRecursionTarget {
value: v.to_sql(),
}
.into());
}
targets.push(v.clone());
}
targets
}
v if is_final(v) => vec![],
v if is_recursion_target(v) => vec![v.clone()],
v => {
return Err(crate::err::Error::InvalidRecursionTarget {
value: v.to_sql(),
}
.into());
}
};
if !values_to_write.is_empty() {
sink.lock().extend(values_to_write);
}
return Ok(value.clone());
}
if let Some(ref cache) = rec_ctx.assembly_cache {
let next_depth = rec_ctx.depth + 1;
return match value {
Value::Array(arr) => {
let mut results = Vec::with_capacity(arr.len());
for elem in arr.iter() {
if is_final(elem) {
continue;
}
let hash = value_hash(elem);
if let Some(cached) = cache.get(&hash) {
results.push(cached.clone());
}
}
let result = clean_iteration(Value::Array(results.into()));
if is_final(&result) && next_depth < rec_ctx.min_depth {
return Err(ControlFlow::Err(anyhow::Error::new(PathEliminationSignal)));
}
Ok(result)
}
v if !is_final(v) => {
let hash = value_hash(v);
let result = cache.get(&hash).cloned().unwrap_or(Value::None);
if is_final(&result) && next_depth < rec_ctx.min_depth {
return Err(ControlFlow::Err(anyhow::Error::new(PathEliminationSignal)));
}
Ok(result)
}
_ => {
if next_depth < rec_ctx.min_depth {
return Err(ControlFlow::Err(anyhow::Error::new(PathEliminationSignal)));
}
Ok(Value::None)
}
};
}
Err(crate::err::Error::UnsupportedRepeatRecurse.into())
})
}
pub(crate) async fn evaluate_recurse_iterative(
start: &Value,
path: &[Arc<dyn PhysicalExpr>],
bounds: RecursionBounds,
body: &Option<Arc<dyn crate::exec::ExecOperator>>,
exec_ctx: &ExecutionContext,
ctx: EvalContext<'_>,
) -> FlowResult<Value> {
let min_depth = bounds.min;
let max_depth = bounds.cap();
if is_final(start) {
return Ok(get_final(start));
}
let mut levels: Vec<Vec<Value>> = vec![vec![start.clone()]];
for d in 0..(max_depth as usize) {
let current_level = &levels[d];
if current_level.is_empty() {
break;
}
let raw_discovered: Vec<Value> = if let Some(body_op) = body {
let futs: Vec<_> = current_level
.iter()
.filter(|val| !is_final(val) && is_recursion_target(val))
.map(|val| {
let body_ctx = exec_ctx.with_current_value(val.clone());
discover_body_targets(body_op, body_ctx)
})
.collect();
let all_discovered = common::eval_buffered(futs).await?;
all_discovered.into_iter().flatten().collect()
} else {
let sink: Arc<parking_lot::Mutex<Vec<Value>>> =
Arc::new(parking_lot::Mutex::new(Vec::new()));
let discovery_ctx = RecursionCtx {
min_depth,
depth: d as u32,
discovery_sink: Some(Arc::clone(&sink)),
assembly_cache: None,
};
let futures: Vec<_> = current_level
.iter()
.filter(|val| !is_final(val))
.map(|val| {
let eval = ctx.with_value(val).with_recursion_ctx(discovery_ctx.clone());
evaluate_physical_path(val, path, eval)
})
.collect();
let eval_results = eval_buffered_all(futures).await;
for result in eval_results {
match result {
Ok(_) => {}
Err(ControlFlow::Err(ref e))
if e.downcast_ref::<PathEliminationSignal>().is_some() => {}
Err(other) => return Err(other),
}
}
std::mem::take(&mut *sink.lock())
};
let mut seen_level: HashSet<u64> = HashSet::new();
let mut next_level = Vec::new();
for v in raw_discovered {
let hash = value_hash(&v);
if seen_level.insert(hash) {
next_level.push(v);
}
}
if next_level.is_empty() {
break;
}
levels.push(next_level);
}
let num_levels = levels.len();
if bounds.errors_on_limit() && num_levels > max_depth as usize {
return Err(crate::err::Error::IdiomRecursionLimitExceeded {
limit: bounds.system_limit,
}
.into());
}
let mut next_cache: HashMap<u64, Value> = HashMap::new();
for d in (0..num_levels).rev() {
let mut current_cache: HashMap<u64, Value> = HashMap::new();
if d as u32 >= max_depth {
for val in &levels[d] {
current_cache.insert(value_hash(val), val.clone());
}
} else {
let cache_arc = Arc::new(next_cache);
let assembly_ctx = RecursionCtx {
min_depth,
depth: d as u32,
discovery_sink: None,
assembly_cache: Some(Arc::clone(&cache_arc)),
};
let eval_values: Vec<&Value> = levels[d]
.iter()
.filter(|val| {
if is_final(val) {
current_cache.insert(value_hash(val), get_final(val));
false
} else {
true
}
})
.collect();
let futures: Vec<_> = eval_values
.iter()
.map(|val| {
let eval = ctx.with_value(val).with_recursion_ctx(assembly_ctx.clone());
evaluate_physical_path(val, path, eval)
})
.collect();
let eval_results = eval_buffered_all(futures).await;
for (val, result) in eval_values.iter().zip(eval_results) {
let result = match result {
Ok(v) => v,
Err(ControlFlow::Err(ref e))
if e.downcast_ref::<PathEliminationSignal>().is_some() =>
{
Value::None
}
Err(other) => return Err(other),
};
current_cache.insert(value_hash(val), result);
}
}
next_cache = current_cache;
}
let start_hash = value_hash(start);
Ok(next_cache.remove(&start_hash).unwrap_or(Value::None))
}