//! RelAlgIR Interpreter: Execute query plans represented as geolog instances. //! //! This module provides a CPU backend that interprets RelAlgIR instances. //! It reads the string diagram structure from a geolog Structure and executes //! the query operations to produce results. //! //! # Architecture //! //! A RelAlgIR instance encodes a query plan as a string diagram: //! - Wire elements are edges carrying data streams (Z-sets of tuples) //! - Op elements are boxes transforming data //! - Composition is encoded by wire sharing (same Wire as output of one Op and input of another) //! //! The interpreter: //! 1. Parses the instance structure to extract operations and wires //! 2. Builds a dependency graph from wire connections //! 3. Topologically sorts operations (respecting DBSP delay semantics) //! 4. Executes each operation in order //! 5. Returns the result on the designated output wire //! //! # Example //! //! ```ignore //! use geolog::query::from_relalg::execute_relalg; //! //! let result = execute_relalg( //! &relalg_instance, // The compiled query plan //! &relalg_theory, // RelAlgIR theory //! &target_structure, // Data to query //! )?; //! ``` use std::collections::{HashMap, VecDeque}; use crate::core::{ElaboratedTheory, Structure}; use crate::id::{NumericId, Slid, get_slid}; use crate::query::backend::Bag; use crate::query::to_relalg::RelAlgInstance; /// Error type for RelAlgIR execution #[derive(Debug, Clone)] pub enum RelAlgError { /// Missing required sort in RelAlgIR theory MissingSortId(String), /// Missing required function in RelAlgIR theory MissingFuncId(String), /// No output wire found NoOutputWire, /// Invalid operation structure InvalidOp(String), /// Cycle detected without delay InstantaneousCycle, /// Unsupported operation Unsupported(String), } impl std::fmt::Display for RelAlgError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::MissingSortId(s) => write!(f, "Missing sort: {s}"), Self::MissingFuncId(s) => write!(f, "Missing function: {s}"), Self::NoOutputWire => write!(f, "No output wire found in plan"), Self::InvalidOp(s) => write!(f, "Invalid operation: {s}"), Self::InstantaneousCycle => write!(f, "Cycle detected without delay"), Self::Unsupported(s) => write!(f, "Unsupported: {s}"), } } } impl std::error::Error for RelAlgError {} /// Cached sort/function IDs from RelAlgIR theory #[allow(dead_code)] // Some IDs are for future use struct RelAlgIds { // Core sorts wire: usize, op: usize, // Operation sorts scan_op: usize, filter_op: usize, distinct_op: usize, negate_op: usize, join_op: usize, union_op: usize, empty_op: usize, delay_op: usize, diff_op: usize, integrate_op: usize, // Predicate sorts pred: usize, true_pred: usize, false_pred: usize, col_eq_pred: usize, const_eq_pred: usize, and_pred: usize, or_pred: usize, // Join condition sorts join_cond: usize, equi_join_cond: usize, cross_join_cond: usize, // Column reference sorts col_ref: usize, col_path: usize, here_path: usize, left_path: usize, right_path: usize, // GeologMeta sorts (for references to target structure) srt: usize, elem: usize, func: usize, } impl RelAlgIds { fn from_theory(theory: &ElaboratedTheory) -> Result { let sig = &theory.theory.signature; let get_sort = |name: &str| -> Result { sig.sorts .iter() .position(|s| s == name) .ok_or_else(|| RelAlgError::MissingSortId(name.to_string())) }; Ok(Self { wire: get_sort("Wire")?, op: get_sort("Op")?, scan_op: get_sort("ScanOp")?, filter_op: get_sort("FilterOp")?, distinct_op: get_sort("DistinctOp")?, negate_op: get_sort("NegateOp")?, join_op: get_sort("JoinOp")?, union_op: get_sort("UnionOp")?, empty_op: get_sort("EmptyOp")?, delay_op: get_sort("DelayOp")?, diff_op: get_sort("DiffOp")?, integrate_op: get_sort("IntegrateOp")?, pred: get_sort("Pred")?, true_pred: get_sort("TruePred")?, false_pred: get_sort("FalsePred")?, col_eq_pred: get_sort("ColEqPred")?, const_eq_pred: get_sort("ConstEqPred")?, and_pred: get_sort("AndPred")?, or_pred: get_sort("OrPred")?, join_cond: get_sort("JoinCond")?, equi_join_cond: get_sort("EquiJoinCond")?, cross_join_cond: get_sort("CrossJoinCond")?, col_ref: get_sort("ColRef")?, col_path: get_sort("ColPath")?, here_path: get_sort("HerePath")?, left_path: get_sort("LeftPath")?, right_path: get_sort("RightPath")?, srt: get_sort("GeologMeta/Srt")?, elem: get_sort("GeologMeta/Elem")?, func: get_sort("GeologMeta/Func")?, }) } } /// Function IDs for navigating RelAlgIR structure #[allow(dead_code)] // Some IDs are for future use struct RelAlgFuncs { // ScanOp accessors scan_op_srt: usize, scan_op_out: usize, // FilterOp accessors filter_op_in: usize, filter_op_out: usize, filter_op_pred: usize, // DistinctOp accessors distinct_op_in: usize, distinct_op_out: usize, // NegateOp accessors negate_op_in: usize, negate_op_out: usize, // JoinOp accessors join_op_left_in: usize, join_op_right_in: usize, join_op_out: usize, join_op_cond: usize, // UnionOp accessors union_op_left_in: usize, union_op_right_in: usize, union_op_out: usize, // EmptyOp accessors empty_op_out: usize, // DelayOp accessors delay_op_in: usize, delay_op_out: usize, // DiffOp accessors diff_op_in: usize, diff_op_out: usize, // IntegrateOp accessors integrate_op_in: usize, integrate_op_out: usize, // Predicate accessors true_pred_pred: usize, false_pred_pred: usize, col_eq_pred_pred: usize, col_eq_pred_left: usize, col_eq_pred_right: usize, const_eq_pred_pred: usize, const_eq_pred_col: usize, const_eq_pred_val: usize, and_pred_pred: usize, and_pred_left: usize, and_pred_right: usize, or_pred_pred: usize, or_pred_left: usize, or_pred_right: usize, // Join condition accessors equi_join_cond_cond: usize, equi_join_cond_left_col: usize, equi_join_cond_right_col: usize, cross_join_cond_cond: usize, // ColRef accessors col_ref_wire: usize, col_ref_path: usize, // ColPath accessors here_path_path: usize, left_path_path: usize, left_path_rest: usize, right_path_path: usize, right_path_rest: usize, } impl RelAlgFuncs { fn from_theory(theory: &ElaboratedTheory) -> Result { let sig = &theory.theory.signature; let get_func = |name: &str| -> Result { sig.func_names .get(name) .copied() .ok_or_else(|| RelAlgError::MissingFuncId(name.to_string())) }; Ok(Self { scan_op_srt: get_func("ScanOp/srt")?, scan_op_out: get_func("ScanOp/out")?, filter_op_in: get_func("FilterOp/in")?, filter_op_out: get_func("FilterOp/out")?, filter_op_pred: get_func("FilterOp/pred")?, distinct_op_in: get_func("DistinctOp/in")?, distinct_op_out: get_func("DistinctOp/out")?, negate_op_in: get_func("NegateOp/in")?, negate_op_out: get_func("NegateOp/out")?, join_op_left_in: get_func("JoinOp/left_in")?, join_op_right_in: get_func("JoinOp/right_in")?, join_op_out: get_func("JoinOp/out")?, join_op_cond: get_func("JoinOp/cond")?, union_op_left_in: get_func("UnionOp/left_in")?, union_op_right_in: get_func("UnionOp/right_in")?, union_op_out: get_func("UnionOp/out")?, empty_op_out: get_func("EmptyOp/out")?, delay_op_in: get_func("DelayOp/in")?, delay_op_out: get_func("DelayOp/out")?, diff_op_in: get_func("DiffOp/in")?, diff_op_out: get_func("DiffOp/out")?, integrate_op_in: get_func("IntegrateOp/in")?, integrate_op_out: get_func("IntegrateOp/out")?, true_pred_pred: get_func("TruePred/pred")?, false_pred_pred: get_func("FalsePred/pred")?, col_eq_pred_pred: get_func("ColEqPred/pred")?, col_eq_pred_left: get_func("ColEqPred/left")?, col_eq_pred_right: get_func("ColEqPred/right")?, const_eq_pred_pred: get_func("ConstEqPred/pred")?, const_eq_pred_col: get_func("ConstEqPred/col")?, const_eq_pred_val: get_func("ConstEqPred/val")?, and_pred_pred: get_func("AndPred/pred")?, and_pred_left: get_func("AndPred/left")?, and_pred_right: get_func("AndPred/right")?, or_pred_pred: get_func("OrPred/pred")?, or_pred_left: get_func("OrPred/left")?, or_pred_right: get_func("OrPred/right")?, equi_join_cond_cond: get_func("EquiJoinCond/cond")?, equi_join_cond_left_col: get_func("EquiJoinCond/left_col")?, equi_join_cond_right_col: get_func("EquiJoinCond/right_col")?, cross_join_cond_cond: get_func("CrossJoinCond/cond")?, col_ref_wire: get_func("ColRef/wire")?, col_ref_path: get_func("ColRef/path")?, here_path_path: get_func("HerePath/path")?, left_path_path: get_func("LeftPath/path")?, left_path_rest: get_func("LeftPath/rest")?, right_path_path: get_func("RightPath/path")?, right_path_rest: get_func("RightPath/rest")?, }) } } /// Parsed operation from a RelAlgIR instance #[derive(Debug, Clone)] enum ParsedOp { Scan { sort_idx: usize, out_wire: Slid, }, Filter { in_wire: Slid, out_wire: Slid, pred: Slid, }, Distinct { in_wire: Slid, out_wire: Slid, }, Negate { in_wire: Slid, out_wire: Slid, }, Join { left_wire: Slid, right_wire: Slid, out_wire: Slid, cond: Slid, }, Union { left_wire: Slid, right_wire: Slid, out_wire: Slid, }, Empty { out_wire: Slid, }, Delay { in_wire: Slid, out_wire: Slid, }, Diff { in_wire: Slid, out_wire: Slid, }, Integrate { in_wire: Slid, out_wire: Slid, }, } impl ParsedOp { fn out_wire(&self) -> Slid { match self { Self::Scan { out_wire, .. } | Self::Filter { out_wire, .. } | Self::Distinct { out_wire, .. } | Self::Negate { out_wire, .. } | Self::Join { out_wire, .. } | Self::Union { out_wire, .. } | Self::Empty { out_wire, .. } | Self::Delay { out_wire, .. } | Self::Diff { out_wire, .. } | Self::Integrate { out_wire, .. } => *out_wire, } } fn in_wires(&self) -> Vec { match self { Self::Scan { .. } | Self::Empty { .. } => vec![], Self::Filter { in_wire, .. } | Self::Distinct { in_wire, .. } | Self::Negate { in_wire, .. } | Self::Delay { in_wire, .. } | Self::Diff { in_wire, .. } | Self::Integrate { in_wire, .. } => vec![*in_wire], Self::Join { left_wire, right_wire, .. } | Self::Union { left_wire, right_wire, .. } => vec![*left_wire, *right_wire], } } /// Returns true if this operation breaks instantaneous cycles fn breaks_cycle(&self) -> bool { matches!(self, Self::Delay { .. } | Self::Integrate { .. }) } } /// Parsed predicate from a RelAlgIR instance #[derive(Debug, Clone)] pub enum ParsedPred { True, False, ColEq { left: usize, right: usize }, ConstEq { col: usize, val: Slid }, And(Box, Box), Or(Box, Box), } /// Parsed join condition #[derive(Debug, Clone)] pub enum ParsedJoinCond { Cross, Equi { left_col: usize, right_col: usize }, } /// Context for interpreting RelAlgIR instances struct InterpretContext<'a> { /// The RelAlgIR instance being interpreted instance: &'a RelAlgInstance, /// RelAlgIR theory sort IDs ids: RelAlgIds, /// RelAlgIR theory function IDs funcs: RelAlgFuncs, /// Wire values during execution wire_values: HashMap, /// Target structure being queried target: &'a Structure, } impl<'a> InterpretContext<'a> { fn new( instance: &'a RelAlgInstance, theory: &ElaboratedTheory, target: &'a Structure, ) -> Result { Ok(Self { instance, ids: RelAlgIds::from_theory(theory)?, funcs: RelAlgFuncs::from_theory(theory)?, wire_values: HashMap::new(), target, }) } /// Get function value for an element fn get_func_value(&self, func_id: usize, elem: Slid) -> Option { let structure = &self.instance.structure; // Convert global Slid to sort-local index let local_idx = structure.sort_local_id(elem).index(); structure .functions .get(func_id) .and_then(|f| get_slid(f.get_local(local_idx))) } /// Get sort index from a GeologMeta/Srt element using the sort_mapping fn get_srt_sort_idx(&self, srt_elem: Slid) -> Result { self.instance .sort_mapping .get(&srt_elem) .copied() .ok_or_else(|| RelAlgError::InvalidOp(format!( "Unknown Srt element {:?} - not in sort_mapping", srt_elem ))) } /// Parse all operations from the instance fn parse_operations(&self) -> Result, RelAlgError> { let mut ops = Vec::new(); let structure = &self.instance.structure; // Find all ScanOp elements for elem_idx in structure.carriers[self.ids.scan_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let srt = self .get_func_value(self.funcs.scan_op_srt, elem) .ok_or_else(|| RelAlgError::InvalidOp("ScanOp missing srt".into()))?; let out_wire = self .get_func_value(self.funcs.scan_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("ScanOp missing out".into()))?; let sort_idx = self.get_srt_sort_idx(srt)?; ops.push(ParsedOp::Scan { sort_idx, out_wire }); } // Find all FilterOp elements for elem_idx in structure.carriers[self.ids.filter_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.filter_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("FilterOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.filter_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("FilterOp missing out".into()))?; let pred = self .get_func_value(self.funcs.filter_op_pred, elem) .ok_or_else(|| RelAlgError::InvalidOp("FilterOp missing pred".into()))?; ops.push(ParsedOp::Filter { in_wire, out_wire, pred, }); } // Find all DistinctOp elements for elem_idx in structure.carriers[self.ids.distinct_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.distinct_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("DistinctOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.distinct_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("DistinctOp missing out".into()))?; ops.push(ParsedOp::Distinct { in_wire, out_wire }); } // Find all NegateOp elements for elem_idx in structure.carriers[self.ids.negate_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.negate_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("NegateOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.negate_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("NegateOp missing out".into()))?; ops.push(ParsedOp::Negate { in_wire, out_wire }); } // Find all JoinOp elements for elem_idx in structure.carriers[self.ids.join_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let left_wire = self .get_func_value(self.funcs.join_op_left_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("JoinOp missing left_in".into()))?; let right_wire = self .get_func_value(self.funcs.join_op_right_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("JoinOp missing right_in".into()))?; let out_wire = self .get_func_value(self.funcs.join_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("JoinOp missing out".into()))?; let cond = self .get_func_value(self.funcs.join_op_cond, elem) .ok_or_else(|| RelAlgError::InvalidOp("JoinOp missing cond".into()))?; ops.push(ParsedOp::Join { left_wire, right_wire, out_wire, cond, }); } // Find all UnionOp elements for elem_idx in structure.carriers[self.ids.union_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let left_wire = self .get_func_value(self.funcs.union_op_left_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("UnionOp missing left_in".into()))?; let right_wire = self .get_func_value(self.funcs.union_op_right_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("UnionOp missing right_in".into()))?; let out_wire = self .get_func_value(self.funcs.union_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("UnionOp missing out".into()))?; ops.push(ParsedOp::Union { left_wire, right_wire, out_wire, }); } // Find all EmptyOp elements for elem_idx in structure.carriers[self.ids.empty_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let out_wire = self .get_func_value(self.funcs.empty_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("EmptyOp missing out".into()))?; ops.push(ParsedOp::Empty { out_wire }); } // Find all DelayOp elements for elem_idx in structure.carriers[self.ids.delay_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.delay_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("DelayOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.delay_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("DelayOp missing out".into()))?; ops.push(ParsedOp::Delay { in_wire, out_wire }); } // Find all DiffOp elements for elem_idx in structure.carriers[self.ids.diff_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.diff_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("DiffOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.diff_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("DiffOp missing out".into()))?; ops.push(ParsedOp::Diff { in_wire, out_wire }); } // Find all IntegrateOp elements for elem_idx in structure.carriers[self.ids.integrate_op].iter() { let elem = Slid::from_usize(elem_idx as usize); let in_wire = self .get_func_value(self.funcs.integrate_op_in, elem) .ok_or_else(|| RelAlgError::InvalidOp("IntegrateOp missing in".into()))?; let out_wire = self .get_func_value(self.funcs.integrate_op_out, elem) .ok_or_else(|| RelAlgError::InvalidOp("IntegrateOp missing out".into()))?; ops.push(ParsedOp::Integrate { in_wire, out_wire }); } Ok(ops) } /// Parse a predicate element fn parse_predicate(&self, pred: Slid) -> Result { // Try to find which sort the predicate element belongs to let structure = &self.instance.structure; // Check if it's TruePred for elem_idx in structure.carriers[self.ids.true_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.true_pred_pred, elem) && p == pred { return Ok(ParsedPred::True); } } // Check if it's FalsePred for elem_idx in structure.carriers[self.ids.false_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.false_pred_pred, elem) && p == pred { return Ok(ParsedPred::False); } } // Check if it's ColEqPred for elem_idx in structure.carriers[self.ids.col_eq_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.col_eq_pred_pred, elem) && p == pred { let left_ref = self .get_func_value(self.funcs.col_eq_pred_left, elem) .ok_or_else(|| RelAlgError::InvalidOp("ColEqPred missing left".into()))?; let right_ref = self .get_func_value(self.funcs.col_eq_pred_right, elem) .ok_or_else(|| RelAlgError::InvalidOp("ColEqPred missing right".into()))?; let left = self.parse_col_ref(left_ref)?; let right = self.parse_col_ref(right_ref)?; return Ok(ParsedPred::ColEq { left, right }); } } // Check if it's ConstEqPred for elem_idx in structure.carriers[self.ids.const_eq_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.const_eq_pred_pred, elem) && p == pred { let col_ref = self .get_func_value(self.funcs.const_eq_pred_col, elem) .ok_or_else(|| RelAlgError::InvalidOp("ConstEqPred missing col".into()))?; let elem_ref = self .get_func_value(self.funcs.const_eq_pred_val, elem) .ok_or_else(|| RelAlgError::InvalidOp("ConstEqPred missing val".into()))?; let col = self.parse_col_ref(col_ref)?; // Look up the original target value from the Elem element let val = self.instance .elem_value_mapping .get(&elem_ref) .copied() .ok_or_else(|| RelAlgError::InvalidOp(format!( "ConstEqPred val {:?} not in elem_value_mapping", elem_ref )))?; return Ok(ParsedPred::ConstEq { col, val }); } } // Check if it's AndPred for elem_idx in structure.carriers[self.ids.and_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.and_pred_pred, elem) && p == pred { let left = self .get_func_value(self.funcs.and_pred_left, elem) .ok_or_else(|| RelAlgError::InvalidOp("AndPred missing left".into()))?; let right = self .get_func_value(self.funcs.and_pred_right, elem) .ok_or_else(|| RelAlgError::InvalidOp("AndPred missing right".into()))?; let left_pred = self.parse_predicate(left)?; let right_pred = self.parse_predicate(right)?; return Ok(ParsedPred::And(Box::new(left_pred), Box::new(right_pred))); } } // Check if it's OrPred for elem_idx in structure.carriers[self.ids.or_pred].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.or_pred_pred, elem) && p == pred { let left = self .get_func_value(self.funcs.or_pred_left, elem) .ok_or_else(|| RelAlgError::InvalidOp("OrPred missing left".into()))?; let right = self .get_func_value(self.funcs.or_pred_right, elem) .ok_or_else(|| RelAlgError::InvalidOp("OrPred missing right".into()))?; let left_pred = self.parse_predicate(left)?; let right_pred = self.parse_predicate(right)?; return Ok(ParsedPred::Or(Box::new(left_pred), Box::new(right_pred))); } } Err(RelAlgError::InvalidOp(format!( "Unknown predicate type for {:?}", pred ))) } /// Parse a join condition element fn parse_join_cond(&self, cond: Slid) -> Result { let structure = &self.instance.structure; // Check if it's CrossJoinCond for elem_idx in structure.carriers[self.ids.cross_join_cond].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(c) = self.get_func_value(self.funcs.cross_join_cond_cond, elem) && c == cond { return Ok(ParsedJoinCond::Cross); } } // Check if it's EquiJoinCond for elem_idx in structure.carriers[self.ids.equi_join_cond].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(c) = self.get_func_value(self.funcs.equi_join_cond_cond, elem) && c == cond { let left_col_ref = self .get_func_value(self.funcs.equi_join_cond_left_col, elem) .ok_or_else(|| { RelAlgError::InvalidOp("EquiJoinCond missing left_col".into()) })?; let right_col_ref = self .get_func_value(self.funcs.equi_join_cond_right_col, elem) .ok_or_else(|| { RelAlgError::InvalidOp("EquiJoinCond missing right_col".into()) })?; let left_col = self.parse_col_ref(left_col_ref)?; let right_col = self.parse_col_ref(right_col_ref)?; return Ok(ParsedJoinCond::Equi { left_col, right_col }); } } Err(RelAlgError::InvalidOp(format!( "Unknown join condition type for {:?}", cond ))) } /// Parse a column reference to get the column index fn parse_col_ref(&self, col_ref: Slid) -> Result { // Get the path from the ColRef let path = self .get_func_value(self.funcs.col_ref_path, col_ref) .ok_or_else(|| RelAlgError::InvalidOp("ColRef missing path".into()))?; self.parse_col_path(path) } /// Parse a column path to get the column index fn parse_col_path(&self, path: Slid) -> Result { let structure = &self.instance.structure; // Check if it's HerePath (index 0) for elem_idx in structure.carriers[self.ids.here_path].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.here_path_path, elem) && p == path { return Ok(0); } } // Check if it's LeftPath for elem_idx in structure.carriers[self.ids.left_path].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.left_path_path, elem) && p == path { let rest = self .get_func_value(self.funcs.left_path_rest, elem) .ok_or_else(|| RelAlgError::InvalidOp("LeftPath missing rest".into()))?; return self.parse_col_path(rest); } } // Check if it's RightPath for elem_idx in structure.carriers[self.ids.right_path].iter() { let elem = Slid::from_usize(elem_idx as usize); if let Some(p) = self.get_func_value(self.funcs.right_path_path, elem) && p == path { let rest = self .get_func_value(self.funcs.right_path_rest, elem) .ok_or_else(|| RelAlgError::InvalidOp("RightPath missing rest".into()))?; // Right path adds 1 to the column index return Ok(1 + self.parse_col_path(rest)?); } } Err(RelAlgError::InvalidOp(format!( "Unknown path type for {:?}", path ))) } /// Topologically sort operations (respecting dependencies) fn topological_sort(&self, ops: &[ParsedOp]) -> Result, RelAlgError> { // Build output wire -> operation index map let mut wire_to_op: HashMap = HashMap::new(); for (idx, op) in ops.iter().enumerate() { wire_to_op.insert(op.out_wire(), idx); } // Build dependency graph let mut in_degree: Vec = vec![0; ops.len()]; let mut dependents: Vec> = vec![Vec::new(); ops.len()]; for (idx, op) in ops.iter().enumerate() { for in_wire in op.in_wires() { if let Some(&producer_idx) = wire_to_op.get(&in_wire) { // Skip delay edges for cycle breaking if !ops[producer_idx].breaks_cycle() { in_degree[idx] += 1; dependents[producer_idx].push(idx); } } } } // Kahn's algorithm let mut queue: VecDeque = VecDeque::new(); for (idx, °ree) in in_degree.iter().enumerate() { if degree == 0 { queue.push_back(idx); } } let mut sorted = Vec::new(); while let Some(idx) = queue.pop_front() { sorted.push(idx); for &dep_idx in &dependents[idx] { in_degree[dep_idx] -= 1; if in_degree[dep_idx] == 0 { queue.push_back(dep_idx); } } } if sorted.len() != ops.len() { return Err(RelAlgError::InstantaneousCycle); } Ok(sorted) } /// Execute a single operation fn execute_op(&mut self, op: &ParsedOp) -> Result { match op { ParsedOp::Scan { sort_idx, .. } => { // Emit all elements of the sort as singleton tuples let mut result = Bag::new(); if let Some(carrier) = self.target.carriers.get(*sort_idx) { for elem in carrier.iter() { let tuple = vec![Slid::from_usize(elem as usize)]; result.insert(tuple, 1); } } Ok(result) } ParsedOp::Filter { in_wire, pred, .. } => { let input = self .wire_values .get(in_wire) .ok_or_else(|| RelAlgError::InvalidOp("Filter input wire not found".into()))? .clone(); let parsed_pred = self.parse_predicate(*pred)?; let mut result = Bag::new(); for (tuple, mult) in input.iter() { if self.evaluate_predicate(&parsed_pred, tuple)? { result.insert(tuple.clone(), *mult); } } Ok(result) } ParsedOp::Distinct { in_wire, .. } => { let input = self .wire_values .get(in_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Distinct input wire not found".into()) })? .clone(); let mut result = Bag::new(); for (tuple, mult) in input.iter() { if *mult > 0 { result.insert(tuple.clone(), 1); } } Ok(result) } ParsedOp::Negate { in_wire, .. } => { let input = self .wire_values .get(in_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Negate input wire not found".into()) })? .clone(); let mut result = Bag::new(); for (tuple, mult) in input.iter() { result.insert(tuple.clone(), -mult); } Ok(result) } ParsedOp::Join { left_wire, right_wire, cond, .. } => { let left = self .wire_values .get(left_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Join left input wire not found".into()) })? .clone(); let right = self .wire_values .get(right_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Join right input wire not found".into()) })? .clone(); let parsed_cond = self.parse_join_cond(*cond)?; let mut result = Bag::new(); match parsed_cond { ParsedJoinCond::Cross => { // Cartesian product for (l_tuple, l_mult) in left.iter() { for (r_tuple, r_mult) in right.iter() { let mut joined = l_tuple.clone(); joined.extend(r_tuple.iter().cloned()); result.insert(joined, l_mult * r_mult); } } } ParsedJoinCond::Equi { left_col, right_col } => { // Hash join let mut right_index: HashMap, i64)>> = HashMap::new(); for (r_tuple, r_mult) in right.iter() { if let Some(&key) = r_tuple.get(right_col) { right_index.entry(key).or_default().push((r_tuple, *r_mult)); } } for (l_tuple, l_mult) in left.iter() { if let Some(&key) = l_tuple.get(left_col) && let Some(matches) = right_index.get(&key) { for (r_tuple, r_mult) in matches { let mut joined = l_tuple.clone(); joined.extend(r_tuple.iter().cloned()); result.insert(joined, l_mult * r_mult); } } } } } Ok(result) } ParsedOp::Union { left_wire, right_wire, .. } => { let left = self .wire_values .get(left_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Union left input wire not found".into()) })? .clone(); let right = self .wire_values .get(right_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Union right input wire not found".into()) })? .clone(); let mut result = left; for (tuple, mult) in right.iter() { result.insert(tuple.clone(), result.tuples.get(tuple).unwrap_or(&0) + mult); } Ok(result) } ParsedOp::Empty { .. } => Ok(Bag::new()), ParsedOp::Delay { in_wire, .. } => { // For non-streaming execution, delay is identity let input = self .wire_values .get(in_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Delay input wire not found".into()) })? .clone(); Ok(input) } ParsedOp::Diff { in_wire, .. } => { // For non-streaming execution, diff is identity let input = self .wire_values .get(in_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Diff input wire not found".into()) })? .clone(); Ok(input) } ParsedOp::Integrate { in_wire, .. } => { // For non-streaming execution, integrate is identity let input = self .wire_values .get(in_wire) .ok_or_else(|| { RelAlgError::InvalidOp("Integrate input wire not found".into()) })? .clone(); Ok(input) } } } /// Evaluate a predicate on a tuple #[allow(clippy::only_used_in_recursion)] fn evaluate_predicate(&self, pred: &ParsedPred, tuple: &[Slid]) -> Result { match pred { ParsedPred::True => Ok(true), ParsedPred::False => Ok(false), ParsedPred::ColEq { left, right } => { let l = tuple.get(*left); let r = tuple.get(*right); Ok(l.is_some() && l == r) } ParsedPred::ConstEq { col, val } => { let c = tuple.get(*col); Ok(c == Some(val)) } ParsedPred::And(left, right) => { Ok(self.evaluate_predicate(left, tuple)? && self.evaluate_predicate(right, tuple)?) } ParsedPred::Or(left, right) => { Ok(self.evaluate_predicate(left, tuple)? || self.evaluate_predicate(right, tuple)?) } } } } /// Execute a RelAlgIR instance against a target structure /// /// # Arguments /// * `instance` - The RelAlgIR instance representing the query plan /// * `relalg_theory` - The RelAlgIR theory /// * `target` - The structure to query /// * `output_wire_name` - Name of the output wire (defaults to "output") /// /// # Returns /// The query result as a Z-set pub fn execute_relalg( instance: &RelAlgInstance, relalg_theory: &ElaboratedTheory, target: &Structure, output_wire_name: Option<&str>, ) -> Result { let mut ctx = InterpretContext::new(instance, relalg_theory, target)?; // Parse all operations let ops = ctx.parse_operations()?; if ops.is_empty() { return Ok(Bag::new()); } // Find output wire - use instance.output_wire by default, or look up by name let output_wire = if let Some(name) = output_wire_name { instance .names .iter() .find(|(_, n)| *n == name) .map(|(slid, _)| *slid) .ok_or(RelAlgError::NoOutputWire)? } else { instance.output_wire }; // Topologically sort operations let sorted = ctx.topological_sort(&ops)?; // Execute in order for &idx in &sorted { let result = ctx.execute_op(&ops[idx])?; let out_wire = ops[idx].out_wire(); ctx.wire_values.insert(out_wire, result); } // Return output wire value ctx.wire_values .remove(&output_wire) .ok_or(RelAlgError::NoOutputWire) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parsed_op_in_wires() { let scan = ParsedOp::Scan { sort_idx: 0, out_wire: Slid::from_usize(0), }; assert!(scan.in_wires().is_empty()); let filter = ParsedOp::Filter { in_wire: Slid::from_usize(0), out_wire: Slid::from_usize(1), pred: Slid::from_usize(2), }; assert_eq!(filter.in_wires(), vec![Slid::from_usize(0)]); let join = ParsedOp::Join { left_wire: Slid::from_usize(0), right_wire: Slid::from_usize(1), out_wire: Slid::from_usize(2), cond: Slid::from_usize(3), }; assert_eq!( join.in_wires(), vec![Slid::from_usize(0), Slid::from_usize(1)] ); } #[test] fn test_parsed_op_breaks_cycle() { let scan = ParsedOp::Scan { sort_idx: 0, out_wire: Slid::from_usize(0), }; assert!(!scan.breaks_cycle()); let delay = ParsedOp::Delay { in_wire: Slid::from_usize(0), out_wire: Slid::from_usize(1), }; assert!(delay.breaks_cycle()); let integrate = ParsedOp::Integrate { in_wire: Slid::from_usize(0), out_wire: Slid::from_usize(1), }; assert!(integrate.breaks_cycle()); } }