Skip to main content

vantage_table/table/impls/
selectable.rs

1use vantage_core::Result;
2use vantage_expressions::traits::selectable::Selectable;
3use vantage_expressions::{Expression, Expressive, SelectableDataSource, expr_any};
4use vantage_types::Entity;
5
6use crate::{
7    column::core::ColumnType, table::Table, traits::column_like::ColumnLike,
8    traits::table_source::TableSource,
9};
10
11impl<T, E> Table<T, E>
12where
13    T: SelectableDataSource<T::Value, T::Condition> + TableSource,
14    T::Value: From<String>, // that's because table is specified as a string
15    E: Entity<T::Value>,
16{
17    /// Create a bare select with source, conditions, ordering, and pagination —
18    /// but no fields. Used by `select_column` and aggregates to avoid evaluating
19    /// all expressions.
20    pub fn select_empty(&self) -> T::Select {
21        let mut select = self.data_source.select();
22        select.add_source(self.table_name(), None);
23
24        for condition in self.conditions.values() {
25            select.add_where_condition(condition.clone());
26        }
27
28        for (expr, direction) in self.order_by.values() {
29            let order = match direction {
30                crate::sorting::SortDirection::Ascending => vantage_expressions::Order::Asc,
31                crate::sorting::SortDirection::Descending => vantage_expressions::Order::Desc,
32            };
33            select.add_order_by(expr.clone(), order);
34        }
35
36        if let Some(pagination) = &self.pagination {
37            select.set_limit(Some(pagination.limit()), Some(pagination.skip()));
38        }
39
40        select
41    }
42
43    /// Create a select query with table configuration applied
44    pub fn select(&self) -> T::Select {
45        let mut select = self.select_empty();
46
47        // Add all columns as fields (or expressions if defined)
48        for column in self.columns.values() {
49            if let Some(expr_fn) = self.expressions.get(column.name()) {
50                let expr = expr_fn(self);
51                self.data_source.add_select_column(
52                    &mut select,
53                    expr_any!("({})", (expr)),
54                    Some(column.name()),
55                );
56            } else if let Some(alias) = column.alias() {
57                let expr = self.data_source.expr(column.name(), vec![]);
58                self.data_source
59                    .add_select_column(&mut select, expr, Some(alias));
60            } else {
61                select.add_field(column.name());
62            }
63        }
64
65        // Add expressions that don't correspond to any column
66        for (name, expr_fn) in &self.expressions {
67            if !self.columns.contains_key(name) {
68                let expr = expr_fn(self);
69                self.data_source.add_select_column(
70                    &mut select,
71                    expr_any!("({})", (expr)),
72                    Some(name),
73                );
74            }
75        }
76
77        select
78    }
79    /// Get count of records in the table
80    pub async fn get_count(&self) -> Result<i64> {
81        self.data_source.get_table_count(self).await
82    }
83
84    /// Get sum of a column in the table
85    pub async fn get_sum(&self, column: &T::Column<T::AnyType>) -> Result<T::Value> {
86        self.data_source.get_table_sum(self, column).await
87    }
88
89    /// Get max of a column in the table
90    pub async fn get_max(&self, column: &T::Column<T::AnyType>) -> Result<T::Value> {
91        self.data_source.get_table_max(self, column).await
92    }
93
94    /// Get min of a column in the table
95    pub async fn get_min(&self, column: &T::Column<T::AnyType>) -> Result<T::Value> {
96        self.data_source.get_table_min(self, column).await
97    }
98
99    /// Create a count query expression (does not execute).
100    /// The result is wrapped in parentheses so it's safe to nest as a subquery.
101    pub fn get_count_query(&self) -> Expression<T::Value> {
102        expr_any!("({})", (self.select_empty().as_count()))
103    }
104
105    /// Create a sum query expression for a column (does not execute).
106    /// The result is wrapped in parentheses so it's safe to nest as a subquery.
107    pub fn get_sum_query<Type>(&self, column: &T::Column<Type>) -> Expression<T::Value>
108    where
109        Type: ColumnType,
110        T::Column<Type>: Expressive<T::Value>,
111    {
112        expr_any!("({})", (self.select_empty().as_sum(column.expr())))
113    }
114
115    /// Create a subquery expression that selects a single column from this table.
116    ///
117    /// Builds `SELECT field FROM table WHERE conditions` — useful as a correlated
118    /// subquery inside `with_expression`:
119    ///
120    /// ```rust,ignore
121    /// .with_expression("category", |t| {
122    ///     t.get_subquery_as::<Category>("category").unwrap()
123    ///         .select_column("name")
124    /// })
125    /// ```
126    pub fn select_column(&self, field: &str) -> Expression<T::Value>
127    where
128        T::Column<T::AnyType>: Expressive<T::Value>,
129        T::Select: Expressive<T::Value>,
130    {
131        let expr = self.get_column_expr(field).unwrap();
132        let mut select = self.select_empty();
133        select.clear_fields();
134        select.clear_order_by();
135        select.add_expression(expr);
136        select.expr()
137    }
138}
139
140// Specific implementation for serde_json::Value that can use QuerySource
141impl<T, E> Table<T, E>
142where
143    T: SelectableDataSource<serde_json::Value, T::Condition>
144        + TableSource<Value = serde_json::Value>
145        + vantage_expressions::traits::datasource::ExprDataSource<serde_json::Value>,
146    T::Value: From<String>,
147    E: Entity<serde_json::Value>,
148{
149    /// Get count using QuerySource for serde_json::Value
150    pub async fn get_count_via_query(&self) -> Result<i64> {
151        let count_query = self.get_count_query();
152        let result = self.data_source.execute(&count_query).await?;
153
154        // Extract count from result - could be {"count": 42} or just 42
155        if let Some(count) = result.get("count").and_then(|v| v.as_i64()) {
156            Ok(count)
157        } else if let Some(count) = result.as_i64() {
158            Ok(count)
159        } else {
160            Ok(0)
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::mocks::mock_table_source::MockTableSource;
169    use serde_json::json;
170    use vantage_expressions::mocks::datasource::MockSelectableDataSource;
171    use vantage_expressions::traits::datasource::ExprDataSource;
172
173    #[tokio::test]
174    async fn test_selectable_functionality() {
175        let mock_select_source = MockSelectableDataSource::new(json!([
176            {"id": "1", "name": "Alice", "age": 30},
177            {"id": "2", "name": "Bob", "age": 25}
178        ]));
179
180        let mock_query_source = vantage_expressions::mocks::mock_builder::new()
181            .on_exact_select("(SELECT COUNT(*) FROM \"users\")", json!(42));
182
183        let table = MockTableSource::new()
184            .with_data(
185                "users",
186                vec![
187                    json!({"id": "1", "name": "Alice", "age": 30}),
188                    json!({"id": "2", "name": "Bob", "age": 25}),
189                ],
190            )
191            .await
192            .with_select_source(mock_select_source)
193            .with_query_source(mock_query_source);
194        let table = Table::<_, vantage_types::EmptyEntity>::new("users", table);
195
196        // Basic select
197        let select = table.select();
198        assert_eq!(select.source(), Some("users"));
199
200        // Validate SQL query generation
201        let query_expr: vantage_expressions::Expression<serde_json::Value> = select.into();
202        assert_eq!(query_expr.preview(), "SELECT * FROM users");
203
204        // Test count query generation
205        let count_query = table.get_count_query();
206        assert_eq!(count_query.preview(), "(SELECT COUNT(*) FROM \"users\")");
207
208        // TODO: This does not work with MockColumn - because it does not implement Expressive
209        // // Test sum query generation
210        // let age_column = table.data_source().create_column::<i64>("age");
211        // let sum_query = table.get_sum_query(&age_column);
212        // assert_eq!(sum_query.preview(), "SELECT SUM(age) FROM \"users\"");
213
214        // Test actual count/sum methods - get_count should return 42 from mock query source
215        let count = table.get_count_via_query().await.unwrap();
216        assert_eq!(count, 42);
217    }
218
219    #[tokio::test]
220    #[should_panic(expected = "MockTableSource select source not set")]
221    async fn test_panics_without_select_source() {
222        let table = Table::<_, vantage_types::EmptyEntity>::new("users", MockTableSource::new());
223        let _select = table.select();
224    }
225
226    #[tokio::test]
227    #[should_panic(expected = "MockTableSource query source not set")]
228    async fn test_panics_without_query_source() {
229        let table = Table::<_, vantage_types::EmptyEntity>::new("users", MockTableSource::new());
230        let query = table.data_source().expr("SELECT COUNT(*)", vec![]);
231        let _result = table.data_source().execute(&query).await;
232    }
233}