//! 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}; #[derive(Debug)] pub enum ExecutionError { UnknownColumn(String), 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 {} pub fn execute(plan: &LogicalPlan, instance: &Instance) -> Result { 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::, _>>()?; rows.push(Row::new(values)); } Ok(ResultSet::new(schema.clone(), rows)) } 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::, _>>()?; rows.push(Row::new(values)); } Ok(ResultSet::new(schema.clone(), rows)) } } } fn eval_predicate( expr: &LogicalExpr, row: &Row, schema: &crate::relational::Schema, ) -> Result { 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 { 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 { match term { Term::Constant(value) => Ok(Value::text(value.clone())), Term::Null(_) => Ok(Value::Null), Term::Variable(_) => Err(ExecutionError::NonGroundScanTerm), } }