2026-04-09 12:38:43 +02:00
|
|
|
//! Minimal execution support for the first SQL slice.
|
|
|
|
|
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
|
|
use crate::chase::{Instance, Term};
|
|
|
|
|
use crate::planner::logical::{LogicalExpr, LogicalPlan};
|
|
|
|
|
use crate::relational::{ResultSet, Row, Value};
|
|
|
|
|
|
2026-04-09 12:50:06 +02:00
|
|
|
/// Errors returned by the current logical-plan executor.
|
2026-04-09 12:38:43 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum ExecutionError {
|
2026-04-09 12:50:06 +02:00
|
|
|
/// A column reference could not be resolved.
|
2026-04-09 12:38:43 +02:00
|
|
|
UnknownColumn(String),
|
2026-04-09 12:50:06 +02:00
|
|
|
/// The scan layer encountered a variable term where a ground value was expected.
|
2026-04-09 12:38:43 +02:00
|
|
|
NonGroundScanTerm,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for ExecutionError {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
Self::UnknownColumn(column) => write!(f, "unknown column `{}`", column),
|
|
|
|
|
Self::NonGroundScanTerm => {
|
|
|
|
|
write!(f, "cannot scan non-ground terms into relational rows")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Error for ExecutionError {}
|
|
|
|
|
|
2026-04-09 12:50:06 +02:00
|
|
|
/// Execute the current logical-plan subset against an instance-backed source.
|
2026-04-09 12:38:43 +02:00
|
|
|
pub fn execute(plan: &LogicalPlan, instance: &Instance) -> Result<ResultSet, ExecutionError> {
|
|
|
|
|
match plan {
|
|
|
|
|
LogicalPlan::Scan { table, schema } => {
|
|
|
|
|
let mut rows = Vec::new();
|
|
|
|
|
for fact in instance.facts_for_predicate(table) {
|
|
|
|
|
let values = fact
|
|
|
|
|
.terms
|
|
|
|
|
.iter()
|
|
|
|
|
.map(value_from_term)
|
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
|
rows.push(Row::new(values));
|
|
|
|
|
}
|
|
|
|
|
Ok(ResultSet::new(schema.clone(), rows))
|
|
|
|
|
}
|
2026-04-10 09:51:01 +02:00
|
|
|
LogicalPlan::CrossJoin {
|
|
|
|
|
left,
|
|
|
|
|
right,
|
|
|
|
|
schema,
|
|
|
|
|
} => {
|
|
|
|
|
let left_result = execute(left, instance)?;
|
|
|
|
|
let right_result = execute(right, instance)?;
|
|
|
|
|
let mut rows = Vec::new();
|
|
|
|
|
|
|
|
|
|
for left_row in left_result.rows() {
|
|
|
|
|
for right_row in right_result.rows() {
|
|
|
|
|
let mut values = left_row.values().to_vec();
|
|
|
|
|
values.extend_from_slice(right_row.values());
|
|
|
|
|
rows.push(Row::new(values));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(ResultSet::new(schema.clone(), rows))
|
|
|
|
|
}
|
2026-04-09 12:38:43 +02:00
|
|
|
LogicalPlan::Filter { input, predicate } => {
|
|
|
|
|
let result = execute(input, instance)?;
|
|
|
|
|
let filtered_rows = result
|
|
|
|
|
.rows()
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|row| eval_predicate(predicate, row, result.schema()).unwrap_or(false))
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(ResultSet::new(result.schema().clone(), filtered_rows))
|
|
|
|
|
}
|
|
|
|
|
LogicalPlan::Project {
|
|
|
|
|
input,
|
|
|
|
|
expressions,
|
|
|
|
|
schema,
|
|
|
|
|
} => {
|
|
|
|
|
let result = execute(input, instance)?;
|
|
|
|
|
let mut rows = Vec::new();
|
|
|
|
|
for row in result.rows() {
|
|
|
|
|
let values = expressions
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|expr| eval_expr(&expr.expr, row, result.schema()))
|
|
|
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
|
rows.push(Row::new(values));
|
|
|
|
|
}
|
|
|
|
|
Ok(ResultSet::new(schema.clone(), rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval_predicate(
|
|
|
|
|
expr: &LogicalExpr,
|
|
|
|
|
row: &Row,
|
|
|
|
|
schema: &crate::relational::Schema,
|
|
|
|
|
) -> Result<bool, ExecutionError> {
|
|
|
|
|
match expr {
|
|
|
|
|
LogicalExpr::Eq(left, right) => Ok(eval_expr(left, row, schema)?
|
|
|
|
|
.sql_eq(&eval_expr(right, row, schema)?)
|
|
|
|
|
.unwrap_or(false)),
|
|
|
|
|
_ => Ok(false),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn eval_expr(
|
|
|
|
|
expr: &LogicalExpr,
|
|
|
|
|
row: &Row,
|
|
|
|
|
schema: &crate::relational::Schema,
|
|
|
|
|
) -> Result<Value, ExecutionError> {
|
|
|
|
|
match expr {
|
|
|
|
|
LogicalExpr::Column(name) => {
|
|
|
|
|
let index = schema
|
|
|
|
|
.index_of(name)
|
|
|
|
|
.ok_or_else(|| ExecutionError::UnknownColumn(name.clone()))?;
|
|
|
|
|
Ok(row.get(index).cloned().unwrap_or(Value::Null))
|
|
|
|
|
}
|
|
|
|
|
LogicalExpr::Literal(value) => Ok(value.clone()),
|
|
|
|
|
LogicalExpr::Eq(left, right) => {
|
|
|
|
|
let left = eval_expr(left, row, schema)?;
|
|
|
|
|
let right = eval_expr(right, row, schema)?;
|
|
|
|
|
Ok(Value::Boolean(left.sql_eq(&right).unwrap_or(false)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn value_from_term(term: &Term) -> Result<Value, ExecutionError> {
|
|
|
|
|
match term {
|
|
|
|
|
Term::Constant(value) => Ok(Value::text(value.clone())),
|
|
|
|
|
Term::Null(_) => Ok(Value::Null),
|
|
|
|
|
Term::Variable(_) => Err(ExecutionError::NonGroundScanTerm),
|
|
|
|
|
}
|
|
|
|
|
}
|