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. A handle this table holds
28    /// no condition for (already removed, or from another table) is
29    /// not-found.
30    pub fn temp_remove_condition(&mut self, handle: ConditionHandle) -> Result<()> {
31        if handle.0 <= 0 {
32            return Err(error!("Cannot remove permanent condition"));
33        }
34        match self.conditions.shift_remove(&handle.0) {
35            Some(_) => Ok(()),
36            None => Err(
37                error!("No temporary condition for this handle", handle = handle.0)
38                    .mark_not_found(),
39            ),
40        }
41    }
42
43    /// Swap the temporary condition `old` for `new`: `old` is removed when
44    /// given, `new` added when given, and the handle of `new` returned. A
45    /// stale `old` fails as [`temp_remove_condition`](Self::temp_remove_condition)
46    /// does, before `new` is added.
47    pub fn replace_temp_condition(
48        &mut self,
49        old: Option<ConditionHandle>,
50        new: Option<T::Condition>,
51    ) -> Result<Option<ConditionHandle>> {
52        if let Some(handle) = old {
53            self.temp_remove_condition(handle)?;
54        }
55        Ok(new.map(|condition| self.temp_add_condition(condition)))
56    }
57
58    /// Get all conditions
59    pub fn conditions(&self) -> impl Iterator<Item = &T::Condition> {
60        self.conditions.values()
61    }
62
63    /// Add a condition using the builder pattern
64    pub fn with_condition(mut self, condition: impl Into<T::Condition>) -> Self {
65        self.add_condition(condition);
66        self
67    }
68
69    /// Add a search condition that matches `value` across
70    /// [`search_columns`](Self::search_columns).
71    pub fn add_search(&mut self, value: &str)
72    where
73        T: Sized,
74    {
75        let condition = self.data_source().search_table_condition(self, value);
76        self.add_condition(condition);
77    }
78
79    /// Add a search condition using the builder pattern.
80    pub fn with_search(mut self, value: &str) -> Self
81    where
82        T: Sized,
83    {
84        self.add_search(value);
85        self
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::mocks::mock_table_source::MockTableSource;
92
93    use super::*;
94    use vantage_expressions::expr_any;
95    use vantage_types::EmptyEntity;
96
97    #[test]
98    fn test_temp_conditions() {
99        let ds = MockTableSource::new();
100        let mut table = Table::<_, EmptyEntity>::new("test", ds);
101
102        // Add permanent condition
103        table.add_condition(expr_any!("perm1"));
104        assert_eq!(table.conditions().count(), 1);
105
106        // Add temp conditions
107        let handle1 = table.temp_add_condition(expr_any!("temp1"));
108        let handle2 = table.temp_add_condition(expr_any!("temp2"));
109        assert_eq!(table.conditions().count(), 3);
110
111        // Remove one temp condition
112        table.temp_remove_condition(handle1).unwrap();
113        assert_eq!(table.conditions().count(), 2);
114
115        // Add another permanent
116        table.add_condition(expr_any!("perm2"));
117        assert_eq!(table.conditions().count(), 3);
118
119        // Remove second temp
120        table.temp_remove_condition(handle2).unwrap();
121        assert_eq!(table.conditions().count(), 2);
122
123        // Verify we have exactly 2 conditions left (both permanent)
124        assert_eq!(table.conditions().count(), 2);
125    }
126
127    #[test]
128    fn literal_equality_condition_registers_an_invariant() {
129        use serde_json::json;
130        use vantage_expressions::{Expression, ExpressiveEnum};
131        let mut table = Table::<_, EmptyEntity>::new("t", MockTableSource::new());
132        table.add_condition(Expression::new(
133            "{} = {}",
134            vec![
135                ExpressiveEnum::Nested(Expression::new("parent", vec![])),
136                ExpressiveEnum::Nested(Expression::new(
137                    "{}",
138                    vec![ExpressiveEnum::Scalar(json!("p1"))],
139                )),
140            ],
141        ));
142        assert_eq!(table.invariants().get("parent"), Some(&json!("p1")));
143        let pattern = "%x%".to_string();
144        let _ = table.temp_add_condition(expr_any!("name LIKE {}", pattern));
145        assert_eq!(
146            table.invariants().len(),
147            1,
148            "temp conditions register nothing"
149        );
150    }
151
152    #[test]
153    fn removing_a_stale_handle_is_not_found() {
154        let mut table = Table::<_, EmptyEntity>::new("test", MockTableSource::new());
155        let handle = table.temp_add_condition(expr_any!("temp"));
156        table.temp_remove_condition(handle).unwrap();
157
158        let err = table.temp_remove_condition(handle).unwrap_err();
159        assert!(err.is_not_found(), "{err}");
160        assert_eq!(table.conditions().count(), 0);
161    }
162
163    #[test]
164    fn replace_temp_condition_swaps_and_clears() {
165        let mut table = Table::<_, EmptyEntity>::new("test", MockTableSource::new());
166        table.add_condition(expr_any!("perm"));
167        let first = table
168            .replace_temp_condition(None, Some(expr_any!("a")))
169            .unwrap();
170        let second = table
171            .replace_temp_condition(first, Some(expr_any!("b")))
172            .unwrap();
173        let previews: Vec<String> = table.conditions().map(|c| c.preview()).collect();
174        assert_eq!(previews, ["perm", "b"]);
175
176        let cleared = table.replace_temp_condition(second, None).unwrap();
177        assert!(cleared.is_none());
178        let previews: Vec<String> = table.conditions().map(|c| c.preview()).collect();
179        assert_eq!(previews, ["perm"]);
180
181        let err = table
182            .replace_temp_condition(first, Some(expr_any!("c")))
183            .unwrap_err();
184        assert!(err.is_not_found(), "{err}");
185        assert_eq!(table.conditions().count(), 1, "a refused swap adds nothing");
186    }
187
188    #[test]
189    fn test_cannot_remove_permanent_condition() {
190        let ds = MockTableSource::new();
191        let mut table = Table::<_, EmptyEntity>::new("test", ds);
192
193        table.add_condition(expr_any!("perm"));
194        let _handle = table.temp_add_condition(expr_any!("temp"));
195
196        // Try to forge a handle to permanent condition (negative ID)
197        let fake_handle = ConditionHandle::new(-1);
198        let result = table.temp_remove_condition(fake_handle);
199        assert!(result.is_err());
200    }
201}