Skip to main content

vantage_table/table/impls/
columns.rs

1use indexmap::IndexMap;
2use vantage_expressions::{Expression, Expressive, traits::datasource::ExprDataSource};
3use vantage_types::Entity;
4
5use crate::{
6    column::core::ColumnType, prelude::ColumnLike, table::Table, traits::table_source::TableSource,
7};
8
9impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
10    /// Add a column to the table (accepts any typed column, converts to `Column<AnyType>`)
11    pub fn add_column<NewColumnType>(&mut self, column: T::Column<NewColumnType>)
12    where
13        NewColumnType: ColumnType,
14    {
15        let name = column.name().to_string();
16
17        if self.columns.contains_key(&name) {
18            panic!("Duplicate column: {}", name);
19        }
20
21        // Convert typed column to Column<AnyType> for storage
22        let any_column = self.data_source.to_any_column(column);
23        self.columns.insert(name, any_column);
24    }
25
26    /// Add a column using builder pattern
27    pub fn with_column<NewColumnType>(mut self, column: T::Column<NewColumnType>) -> Self
28    where
29        NewColumnType: ColumnType,
30    {
31        self.add_column(column);
32        self
33    }
34
35    /// Add a typed column to the table (mutable)
36    pub fn add_column_of<NewColumnType>(&mut self, name: impl Into<String>)
37    where
38        NewColumnType: ColumnType,
39    {
40        let column = self
41            .data_source
42            .create_column::<NewColumnType>(&name.into());
43        self.add_column(column);
44    }
45
46    /// Add an ID column — sets both the column and the id_field flag.
47    pub fn with_id_column(mut self, name: impl Into<String>) -> Self
48    where
49        T::Id: ColumnType,
50    {
51        let name = name.into();
52        self.id_field = Some(name.clone());
53        let column = self.data_source.create_column::<T::Id>(&name);
54        self.add_column(column);
55        self
56    }
57
58    /// Add a typed column to the table (builder pattern)
59    pub fn with_column_of<NewColumnType>(self, name: impl Into<String>) -> Self
60    where
61        NewColumnType: ColumnType,
62    {
63        let column = self
64            .data_source
65            .create_column::<NewColumnType>(&name.into());
66        self.with_column(column)
67    }
68
69    /// Get all columns as type-erased columns (`Column<AnyType>`)
70    pub fn columns(&self) -> &IndexMap<String, T::Column<T::AnyType>> {
71        &self.columns
72    }
73
74    /// Get a typed column by converting from stored `Column<AnyType>`
75    pub fn get_column<Type>(&self, name: &str) -> Option<T::Column<Type>>
76    where
77        Type: ColumnType,
78    {
79        let any_column = self.columns.get(name)?;
80        self.data_source
81            .convert_any_column::<Type>(any_column.clone())
82    }
83
84    /// Get an expression for a column or computed expression by name.
85    ///
86    /// If `name` matches a registered expression (from `with_expression`), evaluates
87    /// and returns it. Otherwise returns the column as an expression. Returns `None`
88    /// if the name doesn't match either.
89    pub fn get_column_expr(&self, name: &str) -> Option<vantage_expressions::Expression<T::Value>>
90    where
91        T::Column<T::AnyType>: vantage_expressions::Expressive<T::Value>,
92    {
93        if let Some(expr_fn) = self.expressions.get(name) {
94            Some(expr_fn(self))
95        } else {
96            use vantage_expressions::Expressive;
97            self.columns.get(name).map(|c| c.expr())
98        }
99    }
100}
101
102impl<T, E> Table<T, E>
103where
104    T: TableSource + ExprDataSource<T::Value>,
105    E: Entity<T::Value> + 'static,
106{
107    /// Expression yielding all values of the named column under the
108    /// table's current conditions.
109    ///
110    /// SQL backends materialise this as a `SELECT col FROM tbl WHERE …`
111    /// subquery (embeddable directly into IN clauses); non-query
112    /// backends wrap a `DeferredFn` that runs `list_table_values` and
113    /// projects the column at execute time.
114    ///
115    /// Panics if `column_name` isn't a column on this table — the
116    /// callsite is meant to be a literal column reference, so a typo
117    /// is a programmer error, not a runtime failure mode worth
118    /// surfacing as `Result`.
119    pub fn column_values_expr(&self, column_name: &str) -> Expression<T::Value> {
120        let col = self
121            .get_column::<T::AnyType>(column_name)
122            .unwrap_or_else(|| panic!("column {column_name:?} not found on table"));
123        self.data_source.column_table_values_expr(self, &col).expr()
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use crate::mocks::mock_column::MockColumn;
131    use crate::prelude::MockTableSource;
132    use serde_json::Value;
133    use vantage_types::EmptyEntity;
134
135    #[test]
136    fn test_add_column() {
137        let ds = MockTableSource::new();
138        let mut table = Table::<MockTableSource, EmptyEntity>::new("test", ds);
139
140        table.add_column(MockColumn::<String>::new("name"));
141
142        assert!(table.columns().contains_key("name"));
143        assert_eq!(table.columns().len(), 1);
144    }
145
146    #[test]
147    fn test_with_column() {
148        let ds = MockTableSource::new();
149        let table = Table::<MockTableSource, EmptyEntity>::new("test", ds)
150            .with_column(MockColumn::<Value>::new("name"))
151            .with_column(MockColumn::<i32>::new("email"));
152
153        assert!(table.columns().contains_key("name"));
154        assert!(table.columns().contains_key("email"));
155        assert_eq!(table.columns().len(), 2);
156    }
157
158    #[test]
159    #[should_panic(expected = "Duplicate column")]
160    fn test_duplicate_column_panics() {
161        let ds = MockTableSource::new();
162        let mut table = Table::<MockTableSource, EmptyEntity>::new("test", ds);
163
164        table.add_column(MockColumn::<String>::new("name"));
165        table.add_column(MockColumn::<String>::new("name")); // Should panic
166    }
167
168    #[test]
169    fn test_with_column_of() {
170        let ds = MockTableSource::new();
171        let table = Table::<MockTableSource, EmptyEntity>::new("test", ds)
172            .with_column_of::<String>("name")
173            .with_column_of::<i64>("age")
174            .with_column_of::<bool>("active");
175
176        assert!(table.columns().contains_key("name"));
177        assert!(table.columns().contains_key("age"));
178        assert!(table.columns().contains_key("active"));
179        assert_eq!(table.columns().len(), 3);
180    }
181
182    #[test]
183    fn test_add_column_of() {
184        let ds = MockTableSource::new();
185        let mut table = Table::<MockTableSource, EmptyEntity>::new("test", ds);
186
187        table.add_column_of::<String>("email");
188        table.add_column_of::<i64>("balance");
189
190        assert!(table.columns().contains_key("email"));
191        assert!(table.columns().contains_key("balance"));
192        assert_eq!(table.columns().len(), 2);
193    }
194
195    #[test]
196    fn test_columns_access() {
197        let ds = MockTableSource::new();
198        let table = Table::<MockTableSource, EmptyEntity>::new("test", ds)
199            .with_column_of::<String>("name")
200            .with_column_of::<i64>("age");
201
202        let columns = table.columns();
203        assert!(columns.contains_key("name"));
204        assert!(columns.contains_key("age"));
205        assert_eq!(columns.len(), 2);
206
207        let name_column = table.columns().get("name");
208        assert!(name_column.is_some());
209        assert_eq!(name_column.unwrap().name(), "name");
210
211        let missing_column = table.columns().get("missing");
212        assert!(missing_column.is_none());
213    }
214}