//! Binding relations: rows over named (variable) columns. //! //! Every operator in this crate (after the initial atom scan) consumes and //! produces [`Relation`]s. Column names are variable names; a value at column //! `i` of a row is the value bound to variable `columns[i]` in that solution. //! //! Column names within a single relation must be unique. Constructors enforce //! this invariant; downstream operators rely on it when matching shared columns //! across two relations. use std::collections::HashSet; use crate::value::Value; #[derive(Debug, Clone)] pub struct Relation { pub columns: Vec, pub rows: Vec>, } fn assert_unique_columns(columns: &[String]) { let mut seen: HashSet<&str> = HashSet::with_capacity(columns.len()); for name in columns { assert!( seen.insert(name.as_str()), "duplicate column name in relation: {name}", ); } } impl Relation { /// # Panics /// Panics if `columns` contains a duplicate name. #[must_use] pub fn new(columns: Vec) -> Self { assert_unique_columns(&columns); Self { columns, rows: Vec::new(), } } /// # Panics /// Panics if `columns` contains a duplicate name, or if any row's length /// differs from `columns.len()`. #[must_use] pub fn from_rows(columns: Vec, rows: Vec>) -> Self { assert_unique_columns(&columns); let arity = columns.len(); for (i, row) in rows.iter().enumerate() { assert_eq!( row.len(), arity, "row {i} arity mismatch: expected {arity}, got {}", row.len(), ); } Self { columns, rows } } /// # Panics /// Panics if `row.len() != self.columns.len()`. pub fn push(&mut self, row: Vec) { assert_eq!( row.len(), self.columns.len(), "row arity mismatch: expected {}, got {}", self.columns.len(), row.len(), ); self.rows.push(row); } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic(expected = "duplicate column name")] fn from_rows_rejects_duplicate_column_names() { let _ = Relation::from_rows(vec!["X".to_string(), "X".to_string()], vec![]); } #[test] #[should_panic(expected = "duplicate column name")] fn new_rejects_duplicate_column_names() { let _ = Relation::new(vec!["X".to_string(), "X".to_string()]); } }