use std::sync::Arc;
use futures::stream;
use surrealdb_types::{SqlFormat, ToSql};
use crate::err::Error;
use crate::exec::context::{ContextLevel, ExecutionContext};
use crate::exec::plan_or_compute::{
block_required_context, evaluate_body_expr, evaluate_expr_at_depth, expr_required_context,
};
use crate::exec::{
AccessMode, CardinalityHint, ExecOperator, FlowResult, OperatorMetrics, ValueBatch,
ValueBatchStream,
};
use crate::expr::{Block, ControlFlow, ControlFlowExt, Expr, Param};
use crate::val::Value;
use crate::val::range::IntegerRangeIter;
#[derive(Debug)]
pub struct ForeachPlan {
pub param: Param,
pub(crate) metrics: Arc<OperatorMetrics>,
pub range: Expr,
pub body: Block,
plan_depth: u32,
}
impl ForeachPlan {
pub(crate) fn new(param: Param, range: Expr, body: Block, plan_depth: u32) -> Self {
Self {
param,
range,
body,
metrics: Arc::new(OperatorMetrics::new()),
plan_depth,
}
}
}
enum ForeachIter {
Array(std::vec::IntoIter<Value>),
Range(std::iter::Map<IntegerRangeIter, fn(i64) -> Value>),
}
impl Iterator for ForeachIter {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
match self {
ForeachIter::Array(iter) => iter.next(),
ForeachIter::Range(iter) => iter.next(),
}
}
}
impl ExecOperator for ForeachPlan {
fn name(&self) -> &'static str {
"Foreach"
}
fn attrs(&self) -> Vec<(String, String)> {
vec![
("param".to_string(), self.param.to_string()),
("statements".to_string(), self.body.0.len().to_string()),
]
}
fn required_context(&self) -> ContextLevel {
expr_required_context(&self.range).max(block_required_context(&self.body))
}
fn access_mode(&self) -> AccessMode {
let range_read_only = self.range.read_only();
let body_read_only = self.body.read_only();
if range_read_only && body_read_only {
AccessMode::ReadOnly
} else {
AccessMode::ReadWrite
}
}
fn cardinality_hint(&self) -> CardinalityHint {
CardinalityHint::AtMostOne
}
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let param = self.param.clone();
let range = self.range.clone();
let body = self.body.clone();
let depth = self.plan_depth + 1;
let ctx = ctx.clone();
let stream =
stream::once(async move { execute_foreach(¶m, &range, &body, &ctx, depth).await });
Ok(Box::pin(stream))
}
fn children(&self) -> Vec<&Arc<dyn ExecOperator>> {
vec![]
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(&self.metrics)
}
fn is_scalar(&self) -> bool {
true
}
}
async fn execute_foreach(
param: &Param,
range: &Expr,
body: &Block,
ctx: &ExecutionContext,
depth: u32,
) -> crate::expr::FlowResult<ValueBatch> {
let range_value = evaluate_expr_at_depth(range, ctx, depth).await?;
let iter = match range_value {
Value::Array(arr) => ForeachIter::Array(arr.into_iter()),
Value::Range(r) => {
let r = r.coerce_to_typed::<i64>().map_err(Error::from).context("Invalid FOR range")?;
ForeachIter::Range(r.iter().map(Value::from))
}
v => {
return Err(ControlFlow::Err(anyhow::Error::new(Error::InvalidStatementTarget {
value: v.to_raw_string(),
})));
}
};
let param_name = param.as_str().to_owned();
for v in iter {
ctx.ctx().expect_not_timedout().await.map_err(ControlFlow::Err)?;
if ctx.cancellation().is_cancelled() {
return Err(ControlFlow::Err(anyhow::anyhow!(crate::err::Error::QueryCancelled)));
}
let mut current_ctx = ctx.with_param(param_name.clone(), v.clone());
for expr in body.0.iter() {
let result = evaluate_body_expr(expr, &mut current_ctx, ¶m_name, &v, depth).await;
match result {
Ok(_) => {
}
Err(ControlFlow::Continue) => {
break;
}
Err(ControlFlow::Break) => {
return Ok(ValueBatch {
values: vec![Value::None],
});
}
Err(ctrl) => {
return Err(ctrl);
}
}
}
}
Ok(ValueBatch {
values: vec![Value::None],
})
}
impl ToSql for ForeachPlan {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
f.push_str("FOR ");
self.param.fmt_sql(f, fmt);
f.push_str(" IN ");
self.range.fmt_sql(f, fmt);
f.push(' ');
self.body.fmt_sql(f, fmt);
}
}