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