use selene_core::Value;
use crate::{
BindingTableColumn, ExecutionPlan, PatternPlan, PipelineOp, SourceSpan, ValueExpr,
analyze::ExprId,
plan::{OuterBindingRef, PlannedSubquery, SubqueryBody},
runtime::{
Binding, BindingTable, BindingTableSchema, EvalCtx, ExecutorError, pattern, plan_runner,
},
};
pub(super) fn eval_exists(
expr: &ValueExpr,
negated: bool,
_span: SourceSpan,
binding: &Binding,
schema: &BindingTableSchema,
ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<Value, ExecutorError> {
let table = execute_subquery(expr, binding, schema, ctx)?;
let exists = !table.is_empty();
Ok(Value::Bool(if negated { !exists } else { exists }))
}
pub(super) fn eval_value_subquery(
expr: &ValueExpr,
_span: SourceSpan,
binding: &Binding,
schema: &BindingTableSchema,
ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<Value, ExecutorError> {
let table = execute_subquery(expr, binding, schema, ctx)?;
let Some(row) = table.rows().first() else {
return Ok(Value::Null);
};
Ok(row.get(0).cloned().unwrap_or(Value::Null))
}
fn execute_subquery(
expr: &ValueExpr,
binding: &Binding,
schema: &BindingTableSchema,
ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<BindingTable, ExecutorError> {
let (expr_id, planned) = planned_subquery(expr, ctx)?;
match &planned.body {
SubqueryBody::Pattern(plan) => {
execute_pattern_subquery(expr_id, planned, plan, binding, schema, ctx)
}
SubqueryBody::Plan(plan) => {
execute_plan_subquery(expr_id, planned, plan, binding, schema, ctx)
}
}
}
fn planned_subquery<'plan>(
expr: &ValueExpr,
ctx: &EvalCtx<'_, '_, '_, 'plan>,
) -> Result<(ExprId, &'plan PlannedSubquery), ExecutorError> {
let expr_id = ctx
.expr_ids
.get(expr)
.ok_or(ExecutorError::ImplementationDefined {
detail: "subquery expression id missing -- analyzer/lowering bug",
})?;
let planned = ctx
.subqueries
.get(expr_id)
.ok_or(ExecutorError::ImplementationDefined {
detail: "subquery plan missing -- analyzer/lowering bug",
})?;
Ok((expr_id, planned))
}
fn execute_pattern_subquery(
expr_id: ExprId,
planned: &PlannedSubquery,
plan: &crate::PatternPlan,
binding: &Binding,
schema: &BindingTableSchema,
ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<BindingTable, ExecutorError> {
let target_schema = ctx
.tx
.cached_subquery_schema(expr_id, || target_schema_for_pattern(planned, plan, schema))?;
if null_outer_binding_is_pattern_binding(planned, binding, schema, plan)? {
return Ok(BindingTable::new(target_schema, Vec::new()));
}
let seed = seed_binding(planned, binding, schema, &target_schema)?;
pattern::execute_pattern_with_seed_and_schema(plan, Some(&seed), target_schema, ctx)
}
fn execute_plan_subquery(
expr_id: ExprId,
planned: &PlannedSubquery,
plan: &ExecutionPlan,
binding: &Binding,
schema: &BindingTableSchema,
ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<BindingTable, ExecutorError> {
let target_schema = ctx
.tx
.cached_subquery_schema(expr_id, || target_schema_for_plan(planned, plan, schema))?;
if null_outer_binding_is_plan_pattern_binding(planned, binding, schema, plan)? {
return Ok(BindingTable::new(target_schema, Vec::new()));
}
let seed = seed_binding(planned, binding, schema, &target_schema)?;
plan_runner::execute_plan_read_only_with_seed(
plan,
Some(BindingTable::new(target_schema, vec![seed])),
ctx.tx,
)
}
fn seed_binding(
planned: &PlannedSubquery,
row: &Binding,
source_schema: &BindingTableSchema,
target_schema: &BindingTableSchema,
) -> Result<Binding, ExecutorError> {
let mut values = vec![Value::Null; target_schema.columns.len()];
for outer in &planned.outer_binding_refs {
let source_index = source_index(source_schema, outer)?;
let value = row.get(source_index).cloned().unwrap_or(Value::Null);
let target_index = pattern::column_index(target_schema, &outer.name).ok_or(
ExecutorError::ImplementationDefined {
detail: "subquery outer binding missing from target row",
},
)?;
values[target_index] = value;
}
Ok(Binding::new(values))
}
fn null_outer_binding_is_pattern_binding(
planned: &PlannedSubquery,
row: &Binding,
source_schema: &BindingTableSchema,
pattern: &PatternPlan,
) -> Result<bool, ExecutorError> {
for outer in &planned.outer_binding_refs {
if !pattern_binds_name(pattern, outer.name.clone()) {
continue;
}
let source_index = source_index(source_schema, outer)?;
if matches!(row.get(source_index), Some(Value::Null) | None) {
return Ok(true);
}
}
Ok(false)
}
fn null_outer_binding_is_plan_pattern_binding(
planned: &PlannedSubquery,
row: &Binding,
source_schema: &BindingTableSchema,
plan: &ExecutionPlan,
) -> Result<bool, ExecutorError> {
for outer in &planned.outer_binding_refs {
if !plan_binds_name(plan, outer.name.clone()) {
continue;
}
let source_index = source_index(source_schema, outer)?;
if matches!(row.get(source_index), Some(Value::Null) | None) {
return Ok(true);
}
}
Ok(false)
}
fn plan_binds_name(plan: &ExecutionPlan, name: selene_core::DbString) -> bool {
plan.pattern_plan
.as_ref()
.is_some_and(|pattern| pattern_binds_name(pattern, name.clone()))
|| plan
.pipeline
.iter()
.any(|op| op_binds_name(op, name.clone()))
}
fn op_binds_name(op: &PipelineOp, name: selene_core::DbString) -> bool {
match op {
PipelineOp::Match(pattern) | PipelineOp::OptionalMatch(pattern) => {
pattern_binds_name(pattern, name)
}
PipelineOp::Union { rhs, .. }
| PipelineOp::Chain(rhs)
| PipelineOp::CorrelatedChain(rhs)
| PipelineOp::ExplainPlan { inner: rhs, .. } => plan_binds_name(rhs, name),
PipelineOp::CallSubquery(subquery) => plan_binds_name(&subquery.body, name),
_ => false,
}
}
fn pattern_binds_name(pattern: &PatternPlan, name: selene_core::DbString) -> bool {
pattern.bindings.iter().any(|binding| binding.name == name)
}
fn target_schema_for_pattern(
planned: &PlannedSubquery,
plan: &crate::PatternPlan,
source_schema: &BindingTableSchema,
) -> Result<BindingTableSchema, ExecutorError> {
let mut schema = pattern::schema_for_pattern(plan);
append_outer_bindings(&mut schema, &planned.outer_binding_refs, source_schema)?;
Ok(schema)
}
fn target_schema_for_plan(
planned: &PlannedSubquery,
plan: &ExecutionPlan,
source_schema: &BindingTableSchema,
) -> Result<BindingTableSchema, ExecutorError> {
let mut schema = plan
.pattern_plan
.as_ref()
.map(pattern::schema_for_pattern)
.unwrap_or_else(|| BindingTableSchema {
columns: Vec::new(),
});
append_outer_bindings(&mut schema, &planned.outer_binding_refs, source_schema)?;
Ok(schema)
}
fn append_outer_bindings(
schema: &mut BindingTableSchema,
outer_binding_refs: &[OuterBindingRef],
source_schema: &BindingTableSchema,
) -> Result<(), ExecutorError> {
for outer in outer_binding_refs {
if pattern::column_index(schema, &outer.name).is_some() {
continue;
}
let source_index = source_index(source_schema, outer)?;
let source_column = &source_schema.columns[source_index];
schema.columns.push(BindingTableColumn {
name: Some(outer.name.clone()),
hidden: None,
ty: source_column.ty.clone(),
});
}
Ok(())
}
fn source_index(
source_schema: &BindingTableSchema,
outer: &OuterBindingRef,
) -> Result<usize, ExecutorError> {
pattern::column_index(source_schema, &outer.name).ok_or(ExecutorError::ImplementationDefined {
detail: "subquery outer binding missing from source row",
})
}