use std::sync::Arc;
use futures::stream;
use crate::exec::parts::recurse::PhysicalRecurseInstruction;
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::exec::{
AccessMode, CardinalityHint, CombineAccessModes, ContextLevel, ExecOperator, ExecutionContext,
FlowResult, OperatorMetrics, ValueBatch, ValueBatchStream, monitor_stream,
};
use crate::val::Value;
mod collect;
mod common;
mod default;
mod path;
mod repeat;
mod shortest;
pub(crate) use repeat::evaluate_repeat_recurse;
#[derive(Debug, Clone)]
pub struct RecursionOp {
pub(crate) body: Option<Arc<dyn ExecOperator>>,
pub(crate) path: Vec<Arc<dyn PhysicalExpr>>,
pub(crate) has_repeat_recurse: bool,
pub(crate) min_depth: u32,
pub(crate) max_depth: Option<u32>,
pub(crate) instruction: PhysicalRecurseInstruction,
pub(crate) inclusive: bool,
pub(crate) metrics: Arc<OperatorMetrics>,
}
impl RecursionOp {
pub(crate) fn new(
body: Option<Arc<dyn ExecOperator>>,
path: Vec<Arc<dyn PhysicalExpr>>,
min_depth: u32,
max_depth: Option<u32>,
instruction: PhysicalRecurseInstruction,
inclusive: bool,
has_repeat_recurse: bool,
) -> Self {
Self {
body,
path,
has_repeat_recurse,
min_depth,
max_depth,
instruction,
inclusive,
metrics: Arc::new(OperatorMetrics::new()),
}
}
fn depth_display(&self) -> String {
match (self.min_depth, self.max_depth) {
(1, Some(1)) => "1".to_string(),
(min, Some(max)) if min == max => format!("{}", min),
(1, Some(max)) => format!("1..{}", max),
(1, None) => "1..".to_string(),
(min, Some(max)) => format!("{}..{}", min, max),
(min, None) => format!("{}..", min),
}
}
}
impl ExecOperator for RecursionOp {
fn name(&self) -> &'static str {
"Recurse"
}
fn attrs(&self) -> Vec<(String, String)> {
let mut attrs = vec![("depth".to_string(), self.depth_display())];
let instr_name = match &self.instruction {
PhysicalRecurseInstruction::Default => "default",
PhysicalRecurseInstruction::Collect => "collect",
PhysicalRecurseInstruction::Path => "path",
PhysicalRecurseInstruction::Shortest {
..
} => "shortest",
};
attrs.push(("instruction".to_string(), instr_name.to_string()));
if self.has_repeat_recurse {
attrs.push(("pattern".to_string(), "tree".to_string()));
}
attrs
}
fn required_context(&self) -> ContextLevel {
let path_ctx =
self.path.iter().map(|p| p.required_context()).max().unwrap_or(ContextLevel::Root);
let instruction_ctx = match &self.instruction {
PhysicalRecurseInstruction::Default
| PhysicalRecurseInstruction::Collect
| PhysicalRecurseInstruction::Path => ContextLevel::Root,
PhysicalRecurseInstruction::Shortest {
target,
} => target.required_context(),
};
path_ctx.max(instruction_ctx)
}
fn access_mode(&self) -> AccessMode {
let path_mode = self.path.iter().map(|p| p.access_mode()).combine_all();
let instruction_mode = match &self.instruction {
PhysicalRecurseInstruction::Default
| PhysicalRecurseInstruction::Collect
| PhysicalRecurseInstruction::Path => AccessMode::ReadOnly,
PhysicalRecurseInstruction::Shortest {
target,
} => target.access_mode(),
};
path_mode.combine(instruction_mode)
}
fn cardinality_hint(&self) -> CardinalityHint {
CardinalityHint::AtMostOne
}
fn children(&self) -> Vec<&Arc<dyn ExecOperator>> {
match &self.body {
Some(body) => vec![body],
None => vec![],
}
}
fn metrics(&self) -> Option<&OperatorMetrics> {
Some(&self.metrics)
}
fn execute(&self, ctx: &ExecutionContext) -> FlowResult<ValueBatchStream> {
let ctx = ctx.clone();
let value = ctx.current_value().cloned().unwrap_or(Value::None);
let bounds = common::RecursionBounds {
min: self.min_depth,
max: self.max_depth,
system_limit: ctx.ctx().config.idiom_recursion_limit,
};
let path = self.path.clone();
let body = self.body.clone();
let inclusive = self.inclusive;
let instruction = self.instruction.clone();
let has_repeat_recurse = self.has_repeat_recurse;
let metrics = Arc::clone(&self.metrics);
let fut = async move {
let eval_ctx = EvalContext::from_exec_ctx(&ctx);
let result = if has_repeat_recurse {
repeat::evaluate_recurse_iterative(
&value,
&path,
bounds,
&body,
&ctx,
eval_ctx.with_value(&value),
)
.await?
} else {
match &instruction {
PhysicalRecurseInstruction::Default => {
default::evaluate_recurse_default(
&value,
&path,
bounds,
eval_ctx.with_value(&value),
)
.await?
}
PhysicalRecurseInstruction::Collect => {
collect::evaluate_recurse_collect(
&value,
&path,
bounds,
inclusive,
eval_ctx.with_value(&value),
)
.await?
}
PhysicalRecurseInstruction::Path => {
path::evaluate_recurse_path(
&value,
&path,
bounds,
inclusive,
eval_ctx.with_value(&value),
)
.await?
}
PhysicalRecurseInstruction::Shortest {
target,
} => {
let target_value = target.evaluate(eval_ctx.with_value(&value)).await?;
shortest::evaluate_recurse_shortest(
&value,
&target_value,
&path,
bounds,
inclusive,
eval_ctx.with_value(&value),
)
.await?
}
}
};
Ok(ValueBatch {
values: vec![result],
})
};
Ok(monitor_stream(Box::pin(stream::once(fut)), "Recurse", &metrics))
}
}