Skip to main content

vantage_table/table/impls/
conditions.rs

1use vantage_core::{Result, error};
2use vantage_types::Entity;
3
4use crate::{conditions::ConditionHandle, table::Table, traits::table_source::TableSource};
5
6impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
7    /// Add a permanent condition. A literal `column = value` condition (see
8    /// [`TableSource::condition_equality`]) also becomes a set invariant.
9    pub fn add_condition(&mut self, condition: impl Into<T::Condition>) {
10        let condition = condition.into();
11        if let Some((column, value)) = self.data_source.condition_equality(&condition) {
12            self.add_invariant(column, value);
13        }
14        let id = -self.next_condition_id;
15        self.next_condition_id += 1;
16        self.conditions.insert(id, condition);
17    }
18
19    /// Add a temporary condition that can be removed later
20    pub fn temp_add_condition(&mut self, condition: impl Into<T::Condition>) -> ConditionHandle {
21        let id = self.next_condition_id;
22        self.next_condition_id += 1;
23        self.conditions.insert(id, condition.into());
24        ConditionHandle::new(id)
25    }
26
27    /// Remove a temporary condition by its handle
28    pub fn temp_remove_condition(&mut self, handle: ConditionHandle) -> Result<()> {
29        if handle.0 <= 0 {
30            return Err(error!("Cannot remove permanent condition"));
31        }
32        self.conditions.shift_remove(&handle.0);
33        Ok(())
34    }
35
36    /// Get all conditions
37    pub fn conditions(&self) -> impl Iterator<Item = &T::Condition> {
38        self.conditions.values()
39    }
40
41    /// Add a condition using the builder pattern
42    pub fn with_condition(mut self, condition: impl Into<T::Condition>) -> Self {
43        self.add_condition(condition);
44        self
45    }
46
47    /// Add a search condition that matches `value` across all columns.
48    pub fn add_search(&mut self, value: &str)
49    where
50        T: Sized,
51    {
52        let condition = self.data_source().search_table_condition(self, value);
53        self.add_condition(condition);
54    }
55
56    /// Add a search condition using the builder pattern.
57    pub fn with_search(mut self, value: &str) -> Self
58    where
59        T: Sized,
60    {
61        self.add_search(value);
62        self
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::mocks::mock_table_source::MockTableSource;
69
70    use super::*;
71    use vantage_expressions::expr_any;
72    use vantage_types::EmptyEntity;
73
74    #[test]
75    fn test_temp_conditions() {
76        let ds = MockTableSource::new();
77        let mut table = Table::<_, EmptyEntity>::new("test", ds);
78
79        // Add permanent condition
80        table.add_condition(expr_any!("perm1"));
81        assert_eq!(table.conditions().count(), 1);
82
83        // Add temp conditions
84        let handle1 = table.temp_add_condition(expr_any!("temp1"));
85        let handle2 = table.temp_add_condition(expr_any!("temp2"));
86        assert_eq!(table.conditions().count(), 3);
87
88        // Remove one temp condition
89        table.temp_remove_condition(handle1).unwrap();
90        assert_eq!(table.conditions().count(), 2);
91
92        // Add another permanent
93        table.add_condition(expr_any!("perm2"));
94        assert_eq!(table.conditions().count(), 3);
95
96        // Remove second temp
97        table.temp_remove_condition(handle2).unwrap();
98        assert_eq!(table.conditions().count(), 2);
99
100        // Verify we have exactly 2 conditions left (both permanent)
101        assert_eq!(table.conditions().count(), 2);
102    }
103
104    #[test]
105    fn literal_equality_condition_registers_an_invariant() {
106        use serde_json::json;
107        use vantage_expressions::{Expression, ExpressiveEnum};
108        let mut table = Table::<_, EmptyEntity>::new("t", MockTableSource::new());
109        table.add_condition(Expression::new(
110            "{} = {}",
111            vec![
112                ExpressiveEnum::Nested(Expression::new("parent", vec![])),
113                ExpressiveEnum::Nested(Expression::new(
114                    "{}",
115                    vec![ExpressiveEnum::Scalar(json!("p1"))],
116                )),
117            ],
118        ));
119        assert_eq!(table.invariants().get("parent"), Some(&json!("p1")));
120        let pattern = "%x%".to_string();
121        let _ = table.temp_add_condition(expr_any!("name LIKE {}", pattern));
122        assert_eq!(
123            table.invariants().len(),
124            1,
125            "temp conditions register nothing"
126        );
127    }
128
129    #[test]
130    fn test_cannot_remove_permanent_condition() {
131        let ds = MockTableSource::new();
132        let mut table = Table::<_, EmptyEntity>::new("test", ds);
133
134        table.add_condition(expr_any!("perm"));
135        let _handle = table.temp_add_condition(expr_any!("temp"));
136
137        // Try to forge a handle to permanent condition (negative ID)
138        let fake_handle = ConditionHandle::new(-1);
139        let result = table.temp_remove_condition(fake_handle);
140        assert!(result.is_err());
141    }
142}