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    /// Build a `field == value` condition with a typed `Self::Value`.
49    ///
50    /// Sibling of [`TableSource::eq_condition`] for the case where the caller already
51    /// has a native-typed value in hand (e.g. `Reference::resolve_from_row`
52    /// pulling a join value out of a `Record<Self::Value>`). Avoids going
53    /// through string serialization and avoids the `Expression → T::Condition`
54    /// coercion that some document backends only support via a panic stub.
55    ///
56    /// Backends that participate in row-based reference traversal override;
57    /// the default returns an error so existing impls compile.
58    fn eq_value_condition(&self, field: &str, value: Self::Value) -> Result<Self::Condition> {
59        let _ = (field, value);
60        Err(vantage_core::error!(
61            "eq_value_condition not implemented for this TableSource"
62        ))
63    }
64
65    /// Create a new column with the given name
66    fn create_column<Type: ColumnType>(&self, name: &str) -> Self::Column<Type>;
67
68    /// Convert a typed column to type-erased column
69    fn to_any_column<Type: ColumnType>(
70        &self,
71        column: Self::Column<Type>,
72    ) -> Self::Column<Self::AnyType>;
73
74    /// Attempt to convert a type-erased column back to typed column
75    fn convert_any_column<Type: ColumnType>(
76        &self,
77        any_column: Self::Column<Self::AnyType>,
78    ) -> Option<Self::Column<Type>>;
79
80    /// Create an expression from a template and parameters, similar to Expression::new
81    fn expr(
82        &self,
83        template: impl Into<String>,
84        parameters: Vec<ExpressiveEnum<Self::Value>>,
85    ) -> Expression<Self::Value>;
86
87    /// Create a search condition for a table (e.g., searches across searchable fields)
88    ///
89    /// Different vendors implement search differently:
90    /// - SQL: `field LIKE '%value%'` (returns Expression)
91    /// - SurrealDB: `field CONTAINS 'value'` (returns Expression)
92    /// - MongoDB: `{ field: { $regex: 'value' } }` (returns MongoCondition)
93    ///
94    /// The implementation should search across appropriate fields in the table.
95    fn search_table_condition<E>(
96        &self,
97        table: &Table<Self, E>,
98        search_value: &str,
99    ) -> Self::Condition
100    where
101        E: Entity<Self::Value>,
102        Self: Sized;
103
104    /// Get all data from a table as Record values with IDs (for ReadableValueSet implementation)
105    async fn list_table_values<E>(
106        &self,
107        table: &Table<Self, E>,
108    ) -> Result<IndexMap<Self::Id, Record<Self::Value>>>
109    where
110        E: Entity<Self::Value>,
111        Self: Sized;
112
113    /// Get a single record by ID as Record value (for ReadableValueSet implementation).
114    ///
115    /// Returns `Ok(None)` when no record exists with the given ID.
116    async fn get_table_value<E>(
117        &self,
118        table: &Table<Self, E>,
119        id: &Self::Id,
120    ) -> Result<Option<Record<Self::Value>>>
121    where
122        E: Entity<Self::Value>,
123        Self: Sized;
124
125    /// Get some data from a table as Record value with ID (for ReadableValueSet implementation)
126    async fn get_table_some_value<E>(
127        &self,
128        table: &Table<Self, E>,
129    ) -> Result<Option<(Self::Id, Record<Self::Value>)>>
130    where
131        E: Entity<Self::Value>,
132        Self: Sized;
133
134    /// Get count of records in the table
135    async fn get_table_count<E>(&self, table: &Table<Self, E>) -> Result<i64>
136    where
137        E: Entity<Self::Value>,
138        Self: Sized;
139
140    /// Get sum of a column in the table (returns native value type)
141    async fn get_table_sum<E>(
142        &self,
143        table: &Table<Self, E>,
144        column: &Self::Column<Self::AnyType>,
145    ) -> Result<Self::Value>
146    where
147        E: Entity<Self::Value>,
148        Self: Sized;
149
150    /// Get maximum value of a column in the table (returns native value type)
151    async fn get_table_max<E>(
152        &self,
153        table: &Table<Self, E>,
154        column: &Self::Column<Self::AnyType>,
155    ) -> Result<Self::Value>
156    where
157        E: Entity<Self::Value>,
158        Self: Sized;
159
160    /// Get minimum value of a column in the table (returns native value type)
161    async fn get_table_min<E>(
162        &self,
163        table: &Table<Self, E>,
164        column: &Self::Column<Self::AnyType>,
165    ) -> Result<Self::Value>
166    where
167        E: Entity<Self::Value>,
168        Self: Sized;
169
170    /// Insert a record as Record value (for WritableValueSet implementation)
171    async fn insert_table_value<E>(
172        &self,
173        table: &Table<Self, E>,
174        id: &Self::Id,
175        record: &Record<Self::Value>,
176    ) -> Result<Record<Self::Value>>
177    where
178        E: Entity<Self::Value>,
179        Self: Sized;
180
181    /// Replace a record as Record value (for WritableValueSet implementation)
182    async fn replace_table_value<E>(
183        &self,
184        table: &Table<Self, E>,
185        id: &Self::Id,
186        record: &Record<Self::Value>,
187    ) -> Result<Record<Self::Value>>
188    where
189        E: Entity<Self::Value>,
190        Self: Sized;
191
192    /// Patch a record as Record value (for WritableValueSet implementation)
193    async fn patch_table_value<E>(
194        &self,
195        table: &Table<Self, E>,
196        id: &Self::Id,
197        partial: &Record<Self::Value>,
198    ) -> Result<Record<Self::Value>>
199    where
200        E: Entity<Self::Value>,
201        Self: Sized;
202
203    /// Delete a record by ID (for WritableValueSet implementation)
204    async fn delete_table_value<E>(&self, table: &Table<Self, E>, id: &Self::Id) -> Result<()>
205    where
206        E: Entity<Self::Value>,
207        Self: Sized;
208
209    /// Delete all records (for WritableValueSet implementation)
210    async fn delete_table_all_values<E>(&self, table: &Table<Self, E>) -> Result<()>
211    where
212        E: Entity<Self::Value>,
213        Self: Sized;
214
215    /// Insert a record and return generated ID (for InsertableValueSet implementation)
216    async fn insert_table_return_id_value<E>(
217        &self,
218        table: &Table<Self, E>,
219        record: &Record<Self::Value>,
220    ) -> Result<Self::Id>
221    where
222        E: Entity<Self::Value>,
223        Self: Sized;
224
225    /// Stream all records from a table as (Id, Record) pairs.
226    ///
227    /// Default implementation wraps `list_table_values` into a stream.
228    /// Backends with native streaming (e.g. REST APIs with pagination)
229    /// can override this to yield records incrementally.
230    #[allow(clippy::type_complexity)]
231    fn stream_table_values<'a, E>(
232        &'a self,
233        table: &Table<Self, E>,
234    ) -> Pin<Box<dyn Stream<Item = Result<(Self::Id, Record<Self::Value>)>> + Send + 'a>>
235    where
236        E: Entity<Self::Value> + 'a,
237        Self: Sized,
238    {
239        let table = table.clone();
240        Box::pin(async_stream::stream! {
241            let records = self.list_table_values(&table).await;
242            match records {
243                Ok(map) => {
244                    for item in map {
245                        yield Ok(item);
246                    }
247                }
248                Err(e) => yield Err(e),
249            }
250        })
251    }
252
253    /// Build a condition for "target_field IN (values of source_column from source_table)".
254    ///
255    /// Used by the relationship traversal system (`get_ref_as`) to filter
256    /// a target table by foreign key values from a source table.
257    ///
258    /// Each backend implements this natively:
259    /// - SQL backends build a subquery: `"id" IN (SELECT "bakery_id" FROM "client" WHERE ...)`
260    /// - MongoDB builds a deferred `{ field: { "$in": [...] } }` document
261    /// - CSV fetches values in memory and builds an IN condition
262    fn related_in_condition<SourceE: Entity<Self::Value> + 'static>(
263        &self,
264        target_field: &str,
265        source_table: &Table<Self, SourceE>,
266        source_column: &str,
267    ) -> Self::Condition
268    where
269        Self: Sized;
270
271    /// Build a correlated condition: `target_table.target_field = source_table.source_column`.
272    ///
273    /// Used by `get_subquery_as` for embedding correlated subqueries inside SELECT
274    /// expressions (e.g. `(SELECT COUNT(*) FROM order WHERE order.client_id = client.id)`).
275    ///
276    /// SQL backends produce table-qualified equality; non-SQL backends may not support
277    /// correlated subqueries and should leave the default (which panics).
278    fn related_correlated_condition(
279        &self,
280        target_table: &str,
281        target_field: &str,
282        source_table: &str,
283        source_column: &str,
284    ) -> Self::Condition {
285        let _ = (target_table, target_field, source_table, source_column);
286        unimplemented!("correlated subqueries not supported by this backend")
287    }
288
289    /// Return an associated expression that, when resolved, yields all values
290    /// of the given typed column from this table (respecting current conditions).
291    ///
292    /// For query-language backends, this can be a subquery expression.
293    /// For simple backends (CSV), this uses a `DeferredFn` that loads data
294    /// and extracts the column values at execution time.
295    ///
296    /// The returned `AssociatedExpression` can be:
297    /// - Executed directly: `.get().await -> Result<Vec<Type>>`
298    /// - Composed into expressions: used via `Expressive` trait in `in_()` conditions
299    ///
300    /// ```rust,ignore
301    /// let fk_col = source.get_column::<String>("bakery_id").unwrap();
302    /// let fk_values = source.data_source().column_table_values_expr(&source, &fk_col);
303    /// // Execute: let ids = fk_values.get().await?;
304    /// // Or compose: target.add_condition(target["id"].in_((fk_values)));
305    /// ```
306    fn column_table_values_expr<'a, E, Type: ColumnType>(
307        &'a self,
308        table: &Table<Self, E>,
309        column: &Self::Column<Type>,
310    ) -> AssociatedExpression<'a, Self, Self::Value, Vec<Type>>
311    where
312        E: Entity<Self::Value> + 'static,
313        Self: ExprDataSource<Self::Value> + Sized;
314}