use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::executor::expression::ExpressionError;
use crate::executor::streaming::executor::StreamingExecutor;
use crate::executor::streaming::instance::QueryBindings;
use crate::executor::streaming::plan::materializer::PhysicalPlanMaterializer;
use crate::executor::streaming::plan::types::PhysicalPlan;
use crate::executor::streaming::runtime::ExecutionRuntime;
use crate::executor::streaming::slot::SlotLayout;
use linkrs_core::error::QueryError;
use linkrs_core::types::operators::AggregateFunction;
use linkrs_core::{Expression, Value};
use parking_lot::Mutex;
#[derive(Debug, Clone)]
pub struct SubqueryRunnerSpec {
pub id: u64,
pub plan: Arc<PhysicalPlan>,
pub correlated: bool,
pub group_join: Option<GroupJoinSpec>,
}
#[derive(Debug, Clone)]
pub struct GroupJoinSpec {
pub hash_keys: Vec<Expression>,
pub key_columns: usize,
pub function: AggregateFunction,
pub distinct: bool,
}
#[derive(Debug)]
pub(crate) enum SubqueryCache {
Exists(bool),
Contains(HashSet<Value>),
Scalar(Value),
}
#[derive(Debug)]
pub struct SubqueryRunner {
pub id: u64,
pub plan: Arc<PhysicalPlan>,
pub correlated: bool,
pub group_join: Option<GroupJoinSpec>,
pub(crate) executor: Mutex<Option<StreamingExecutor>>,
pub(crate) cache: Mutex<Option<SubqueryCache>>,
pub(crate) group_table: Mutex<Option<HashMap<Vec<Value>, Value>>>,
pub reset_fallback: Mutex<bool>,
}
impl SubqueryRunner {
pub(super) fn from_spec(spec: &SubqueryRunnerSpec) -> Self {
Self {
id: spec.id,
plan: spec.plan.clone(),
correlated: spec.correlated,
group_join: spec.group_join.clone(),
executor: Mutex::new(None),
cache: Mutex::new(None),
group_table: Mutex::new(None),
reset_fallback: Mutex::new(false),
}
}
pub(super) fn with_executor<F, T>(
&self,
runtime: &Arc<ExecutionRuntime>,
bindings: &Arc<QueryBindings>,
layout: Arc<SlotLayout>,
row: Vec<Value>,
f: F,
) -> Result<T, ExpressionError>
where
F: FnOnce(&mut StreamingExecutor) -> Result<T, QueryError>,
{
let mut slot = self.executor.lock();
if slot.is_none() {
let (mut exec, _) = PhysicalPlanMaterializer::materialize(&self.plan, bindings)
.map_err(|e| {
ExpressionError::type_error(format!(
"Subquery plan materialization failed: {}",
e
))
})?;
exec.set_chunk_size(bindings.chunk_size);
exec.set_runtime(Some(runtime.clone()));
exec.open()
.map_err(|e| ExpressionError::type_error(format!("Subquery open failed: {}", e)))?;
*slot = Some(exec);
}
let exec = slot.as_mut().ok_or_else(|| {
ExpressionError::type_error("Subquery executor failed to materialize".to_string())
})?;
if self.correlated {
exec.inject_correlation_frame(layout, row);
}
exec.reset()
.map_err(|e| ExpressionError::type_error(format!("Subquery reset failed: {}", e)))?;
if exec.base().reset_used_fallback {
*self.reset_fallback.lock() = true;
}
f(exec)
.map_err(|e| ExpressionError::type_error(format!("Subquery execution failed: {}", e)))
}
}