Skip to main content

vantage_table/traits/
table_source.rs

1use std::hash::Hash;
2use std::pin::Pin;
3
4use async_trait::async_trait;
5use futures_core::Stream;
6use indexmap::IndexMap;
7use vantage_dataset::traits::Result;
8use vantage_expressions::{
9    Expression,
10    traits::associated_expressions::AssociatedExpression,
11    traits::datasource::{DataSource, ExprDataSource},
12    traits::expressive::ExpressiveEnum,
13};
14use vantage_types::{Entity, Record};
15
16use crate::{column::core::ColumnType, table::Table, traits::column_like::ColumnLike};
17
18/// Trait for table data sources that defines column type separate from execution
19/// TableSource represents a data source that can create and manage tables
20#[async_trait]
21pub trait TableSource: DataSource + Clone + 'static {
22    type Column<Type>: ColumnLike<Type> + Clone
23    where
24        Type: ColumnType;
25    type AnyType: ColumnType;
26    type Value: Clone + Send + Sync + 'static;
27    type Id: Send + Sync + Clone + Hash + Eq + 'static;
28
29    /// The condition type stored by `Table`. SQL/SurrealDB backends use
30    /// `Expression<Self::Value>`; document-oriented backends like MongoDB
31    /// can use a native filter type (e.g. `bson::Document`).
32    type Condition: Clone + Send + Sync + 'static;
33
34    /// Build a textual `field == value` condition.
35    ///
36    /// Generic UIs (e.g. CLI argument parsers) only have strings on
37    /// hand, so they need a way to construct a `Self::Condition` without
38    /// knowing the backend's native expression type. Backends that
39    /// don't support raw text filtering can leave the default, which
40    /// returns an error.
41    fn eq_condition(field: &str, value: &str) -> Result<Self::Condition> {
42        let _ = (field, value);
43        Err(vantage_core::error!(
44            "eq_condition not implemented for this TableSource"
45        ))
46    }
47
48    /// Create a new column with the given name
49    fn create_column<Type: ColumnType>(&self, name: &str) -> Self::Column<Type>;
50
51    /// Convert a typed column to type-erased column
52    fn to_any_column<Type: ColumnType>(
53        &self,
54        column: Self::Column<Type>,
55    ) -> Self::Column<Self::AnyType>;
56
57    /// Attempt to convert a type-erased column back to typed column
58    fn convert_any_column<Type: ColumnType>(
59        &self,
60        any_column: Self::Column<Self::AnyType>,
61    ) -> Option<Self::Column<Type>>;
62
63    /// Create an expression from a template and parameters, similar to Expression::new
64    fn expr(
65        &self,
66        template: impl Into<String>,
67        parameters: Vec<ExpressiveEnum<Self::Value>>,
68    ) -> Expression<Self::Value>;
69
70    /// Create a search condition for a table (e.g., searches across searchable fields)
71    ///
72    /// Different vendors implement search differently:
73    /// - SQL: `field LIKE '%value%'` (returns Expression)
74    /// - SurrealDB: `field CONTAINS 'value'` (returns Expression)
75    /// - MongoDB: `{ field: { $regex: 'value' } }` (returns MongoCondition)
76    ///
77    /// The implementation should search across appropriate fields in the table.
78    fn search_table_condition<E>(
79        &self,
80        table: &Table<Self, E>,
81        search_value: &str,
82    ) -> Self::Condition
83    where
84        E: Entity<Self::Value>,
85        Self: Sized;
86
87    /// Get all data from a table as Record values with IDs (for ReadableValueSet implementation)
88    async fn list_table_values<E>(
89        &self,
90        table: &Table<Self, E>,
91    ) -> Result<IndexMap<Self::Id, Record<Self::Value>>>
92    where
93        E: Entity<Self::Value>,
94        Self: Sized;
95
96    /// Get a single record by ID as Record value (for ReadableValueSet implementation).
97    ///
98    /// Returns `Ok(None)` when no record exists with the given ID.
99    async fn get_table_value<E>(
100        &self,
101        table: &Table<Self, E>,
102        id: &Self::Id,
103    ) -> Result<Option<Record<Self::Value>>>
104    where
105        E: Entity<Self::Value>,
106        Self: Sized;
107
108    /// Get some data from a table as Record value with ID (for ReadableValueSet implementation)
109    async fn get_table_some_value<E>(
110        &self,
111        table: &Table<Self, E>,
112    ) -> Result<Option<(Self::Id, Record<Self::Value>)>>
113    where
114        E: Entity<Self::Value>,
115        Self: Sized;
116
117    /// Get count of records in the table
118    async fn get_table_count<E>(&self, table: &Table<Self, E>) -> Result<i64>
119    where
120        E: Entity<Self::Value>,
121        Self: Sized;
122
123    /// Get sum of a column in the table (returns native value type)
124    async fn get_table_sum<E>(
125        &self,
126        table: &Table<Self, E>,
127        column: &Self::Column<Self::AnyType>,
128    ) -> Result<Self::Value>
129    where
130        E: Entity<Self::Value>,
131        Self: Sized;
132
133    /// Get maximum value of a column in the table (returns native value type)
134    async fn get_table_max<E>(
135        &self,
136        table: &Table<Self, E>,
137        column: &Self::Column<Self::AnyType>,
138    ) -> Result<Self::Value>
139    where
140        E: Entity<Self::Value>,
141        Self: Sized;
142
143    /// Get minimum value of a column in the table (returns native value type)
144    async fn get_table_min<E>(
145        &self,
146        table: &Table<Self, E>,
147        column: &Self::Column<Self::AnyType>,
148    ) -> Result<Self::Value>
149    where
150        E: Entity<Self::Value>,
151        Self: Sized;
152
153    /// Insert a record as Record value (for WritableValueSet implementation)
154    async fn insert_table_value<E>(
155        &self,
156        table: &Table<Self, E>,
157        id: &Self::Id,
158        record: &Record<Self::Value>,
159    ) -> Result<Record<Self::Value>>
160    where
161        E: Entity<Self::Value>,
162        Self: Sized;
163
164    /// Replace a record as Record value (for WritableValueSet implementation)
165    async fn replace_table_value<E>(
166        &self,
167        table: &Table<Self, E>,
168        id: &Self::Id,
169        record: &Record<Self::Value>,
170    ) -> Result<Record<Self::Value>>
171    where
172        E: Entity<Self::Value>,
173        Self: Sized;
174
175    /// Patch a record as Record value (for WritableValueSet implementation)
176    async fn patch_table_value<E>(
177        &self,
178        table: &Table<Self, E>,
179        id: &Self::Id,
180        partial: &Record<Self::Value>,
181    ) -> Result<Record<Self::Value>>
182    where
183        E: Entity<Self::Value>,
184        Self: Sized;
185
186    /// Delete a record by ID (for WritableValueSet implementation)
187    async fn delete_table_value<E>(&self, table: &Table<Self, E>, id: &Self::Id) -> Result<()>
188    where
189        E: Entity<Self::Value>,
190        Self: Sized;
191
192    /// Delete all records (for WritableValueSet implementation)
193    async fn delete_table_all_values<E>(&self, table: &Table<Self, E>) -> Result<()>
194    where
195        E: Entity<Self::Value>,
196        Self: Sized;
197
198    /// Insert a record and return generated ID (for InsertableValueSet implementation)
199    async fn insert_table_return_id_value<E>(
200        &self,
201        table: &Table<Self, E>,
202        record: &Record<Self::Value>,
203    ) -> Result<Self::Id>
204    where
205        E: Entity<Self::Value>,
206        Self: Sized;
207
208    /// Stream all records from a table as (Id, Record) pairs.
209    ///
210    /// Default implementation wraps `list_table_values` into a stream.
211    /// Backends with native streaming (e.g. REST APIs with pagination)
212    /// can override this to yield records incrementally.
213    #[allow(clippy::type_complexity)]
214    fn stream_table_values<'a, E>(
215        &'a self,
216        table: &Table<Self, E>,
217    ) -> Pin<Box<dyn Stream<Item = Result<(Self::Id, Record<Self::Value>)>> + Send + 'a>>
218    where
219        E: Entity<Self::Value> + 'a,
220        Self: Sized,
221    {
222        let table = table.clone();
223        Box::pin(async_stream::stream! {
224            let records = self.list_table_values(&table).await;
225            match records {
226                Ok(map) => {
227                    for item in map {
228                        yield Ok(item);
229                    }
230                }
231                Err(e) => yield Err(e),
232            }
233        })
234    }
235
236    /// Build a condition for "target_field IN (values of source_column from source_table)".
237    ///
238    /// Used by the relationship traversal system (`get_ref_as`) to filter
239    /// a target table by foreign key values from a source table.
240    ///
241    /// Each backend implements this natively:
242    /// - SQL backends build a subquery: `"id" IN (SELECT "bakery_id" FROM "client" WHERE ...)`
243    /// - MongoDB builds a deferred `{ field: { "$in": [...] } }` document
244    /// - CSV fetches values in memory and builds an IN condition
245    fn related_in_condition<SourceE: Entity<Self::Value> + 'static>(
246        &self,
247        target_field: &str,
248        source_table: &Table<Self, SourceE>,
249        source_column: &str,
250    ) -> Self::Condition
251    where
252        Self: Sized;
253
254    /// Build a correlated condition: `target_table.target_field = source_table.source_column`.
255    ///
256    /// Used by `get_subquery_as` for embedding correlated subqueries inside SELECT
257    /// expressions (e.g. `(SELECT COUNT(*) FROM order WHERE order.client_id = client.id)`).
258    ///
259    /// SQL backends produce table-qualified equality; non-SQL backends may not support
260    /// correlated subqueries and should leave the default (which panics).
261    fn related_correlated_condition(
262        &self,
263        target_table: &str,
264        target_field: &str,
265        source_table: &str,
266        source_column: &str,
267    ) -> Self::Condition {
268        let _ = (target_table, target_field, source_table, source_column);
269        unimplemented!("correlated subqueries not supported by this backend")
270    }
271
272    /// Return an associated expression that, when resolved, yields all values
273    /// of the given typed column from this table (respecting current conditions).
274    ///
275    /// For query-language backends, this can be a subquery expression.
276    /// For simple backends (CSV), this uses a `DeferredFn` that loads data
277    /// and extracts the column values at execution time.
278    ///
279    /// The returned `AssociatedExpression` can be:
280    /// - Executed directly: `.get().await -> Result<Vec<Type>>`
281    /// - Composed into expressions: used via `Expressive` trait in `in_()` conditions
282    ///
283    /// ```rust,ignore
284    /// let fk_col = source.get_column::<String>("bakery_id").unwrap();
285    /// let fk_values = source.data_source().column_table_values_expr(&source, &fk_col);
286    /// // Execute: let ids = fk_values.get().await?;
287    /// // Or compose: target.add_condition(target["id"].in_((fk_values)));
288    /// ```
289    fn column_table_values_expr<'a, E, Type: ColumnType>(
290        &'a self,
291        table: &Table<Self, E>,
292        column: &Self::Column<Type>,
293    ) -> AssociatedExpression<'a, Self, Self::Value, Vec<Type>>
294    where
295        E: Entity<Self::Value> + 'static,
296        Self: ExprDataSource<Self::Value> + Sized;
297}