Skip to main content

vantage_table/table/
base.rs

1use std::marker::PhantomData;
2use std::sync::Arc;
3
4use indexmap::IndexMap;
5use vantage_expressions::Expression;
6use vantage_types::{EmptyEntity, Entity};
7
8use crate::{
9    pagination::Pagination, references::Reference, sorting::SortDirection,
10    traits::table_source::TableSource, traits::table_source_spec::TableSourceSpec,
11};
12
13/// Type alias for expression closures stored on Table.
14///
15/// Stored against the entity-erased `Table<T, EmptyEntity>` rather than the
16/// concrete `Table<T, E>` so the closures survive [`Table::into_entity`] — an
17/// expression only ever reads entity-agnostic table state (columns and
18/// relations by name, conditions, subqueries), never the entity's typed fields.
19/// [`Table::with_expression`] adapts the caller's `Fn(&Table<T, E>)` into this
20/// shape; see `Table::as_entity_erased` for the soundness of the cast.
21pub type ExpressionFn<T> =
22    Arc<dyn Fn(&Table<T, EmptyEntity>) -> Expression<<T as TableSource>::Value> + Send + Sync>;
23
24#[derive(Clone)]
25pub struct Table<T, E>
26where
27    T: TableSource,
28    E: Entity<T::Value>,
29{
30    pub(super) data_source: T,
31    pub(super) _phantom: PhantomData<E>,
32    pub(super) source: T::Source,
33    pub(super) columns: IndexMap<String, T::Column<T::AnyType>>,
34    pub(super) conditions: IndexMap<i64, T::Condition>,
35    pub(super) next_condition_id: i64,
36    pub(super) order_by: IndexMap<i64, (T::Condition, SortDirection)>,
37    pub(super) next_order_id: i64,
38    pub(super) refs: Option<IndexMap<String, Arc<dyn Reference>>>,
39    pub(super) contained: Vec<crate::references::ContainedRelation<T>>,
40    pub(super) expressions: IndexMap<String, ExpressionFn<T>>,
41    pub(super) pagination: Option<Pagination>,
42    pub(super) title_field: Option<String>,
43    pub(super) title_fields: Vec<String>,
44    pub(super) id_field: Option<String>,
45    /// Column values every row in this set must hold, because they are part of
46    /// the set's definition (e.g. a has-many child carries the parent's foreign
47    /// key). Registered wherever the table is narrowed by a literal
48    /// `column = value` (see [`Self::with_id`], `Reference::resolve_from_row`);
49    /// never from an expression scope. Enforced on write: a column the caller
50    /// left null/absent is filled, a matching value is kept, and a conflicting
51    /// value is rejected.
52    pub(super) invariants: IndexMap<String, T::Value>,
53}
54
55impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
56    /// Create a new Table with the given table name and data source
57    pub fn new(table_name: impl Into<String>, data_source: T) -> Self {
58        Self {
59            data_source,
60            _phantom: PhantomData,
61            source: T::Source::from_name(table_name.into()),
62            columns: IndexMap::new(),
63            conditions: IndexMap::new(),
64            next_condition_id: 1,
65            order_by: IndexMap::new(),
66            next_order_id: 1,
67            refs: None,
68            contained: Vec::new(),
69            expressions: IndexMap::new(),
70            pagination: None,
71            title_field: None,
72            title_fields: Vec::new(),
73            id_field: None,
74            invariants: IndexMap::new(),
75        }
76    }
77
78    /// Convert this table to use a different entity type.
79    ///
80    /// Computed expressions are carried over — they're stored entity-erased
81    /// (see [`ExpressionFn`]), so aggregates survive reference traversal that
82    /// erases the entity to `EmptyEntity` (e.g. `get_ref_from_row`).
83    pub fn into_entity<E2: Entity<T::Value>>(self) -> Table<T, E2> {
84        Table {
85            data_source: self.data_source,
86            _phantom: PhantomData,
87            source: self.source,
88            columns: self.columns,
89            conditions: self.conditions,
90            next_condition_id: self.next_condition_id,
91            order_by: self.order_by,
92            next_order_id: self.next_order_id,
93            refs: self.refs,
94            contained: self.contained,
95            expressions: self.expressions,
96            pagination: self.pagination,
97            title_field: self.title_field,
98            title_fields: self.title_fields,
99            id_field: self.id_field,
100            invariants: self.invariants,
101        }
102    }
103
104    /// Borrow this table as its entity-erased form `Table<T, EmptyEntity>`.
105    ///
106    /// `E` appears in `Table` only as `PhantomData<E>` (a zero-sized field), so
107    /// `Table<T, E>` and `Table<T, EmptyEntity>` are layout-identical and this
108    /// reinterpret is sound. Used to feed `self` to the entity-erased
109    /// [`ExpressionFn`] closures at evaluation time.
110    pub(crate) fn as_entity_erased(&self) -> &Table<T, EmptyEntity> {
111        // SAFETY: identical layout (E is PhantomData only); lifetime is tied to
112        // `&self`, and the borrow is shared/read-only.
113        unsafe { &*(self as *const Table<T, E> as *const Table<T, EmptyEntity>) }
114    }
115
116    /// Snapshot the table's relations as Vista references (name, target type,
117    /// cardinality, foreign key). Driver factories fold this into
118    /// `VistaMetadata` so the erased `Vista` carries enough to drive nested
119    /// insert and relation traversal.
120    pub fn vista_references(&self) -> Vec<vantage_vista::Reference> {
121        self.refs
122            .as_ref()
123            .map(|refs| {
124                refs.iter()
125                    .map(|(name, r)| {
126                        vantage_vista::Reference::new(
127                            name.clone(),
128                            r.target_type_name().to_string(),
129                            r.cardinality(),
130                            r.foreign_key().to_string(),
131                        )
132                    })
133                    .collect()
134            })
135            .unwrap_or_default()
136    }
137
138    /// Shape-only specs (name, host, kind, id) for the contained relations
139    /// declared on this table, for driver factories to fold into
140    /// `VistaMetadata`. Columns are derived at traversal from each relation's
141    /// `build_target` closure.
142    pub fn vista_contained(&self) -> Vec<vantage_vista::ContainedSpec> {
143        self.contained.iter().map(|c| c.spec()).collect()
144    }
145
146    /// Look up a contained relation by name (for the driver's traversal).
147    pub fn contained_relation(
148        &self,
149        name: &str,
150    ) -> Option<&crate::references::ContainedRelation<T>> {
151        self.contained.iter().find(|c| c.name() == name)
152    }
153
154    /// Use a callback with a builder pattern for configuration
155    pub fn with<F>(mut self, func: F) -> Self
156    where
157        F: FnOnce(&mut Self),
158    {
159        func(&mut self);
160        self
161    }
162
163    /// Get the table name.
164    ///
165    /// For a query-sourced table this is its FROM alias.
166    pub fn table_name(&self) -> &str {
167        self.source.name()
168    }
169
170    /// The table's source (a name, or a query used as a derived source).
171    pub fn source(&self) -> &T::Source {
172        &self.source
173    }
174
175    /// Override the table name. Used by REST API drivers to swap a
176    /// canonical resource path for a per-reference URI template at
177    /// traversal time.
178    ///
179    /// This replaces the source with a name-based one, so it must not be
180    /// called on a query-sourced (derived) table.
181    pub fn set_table_name(&mut self, name: impl Into<String>) {
182        self.source = T::Source::from_name(name.into());
183    }
184
185    /// Get the underlying data source
186    pub fn data_source(&self) -> &T {
187        &self.data_source
188    }
189
190    /// Get the title field column if set
191    pub fn title_field(&self) -> Option<&T::Column<T::AnyType>> {
192        self.title_field
193            .as_ref()
194            .and_then(|name| self.columns.get(name))
195    }
196
197    /// Names of columns marked as display titles (set via
198    /// [`Self::with_title_column_of`]). These show alongside the id in
199    /// list views and on the leading lines of single-record displays.
200    pub fn title_fields(&self) -> &[String] {
201        &self.title_fields
202    }
203
204    /// Get the id field column if set
205    pub fn id_field(&self) -> Option<&T::Column<T::AnyType>> {
206        self.id_field
207            .as_ref()
208            .and_then(|name| self.columns.get(name))
209    }
210
211    /// Mark an already-added column as the id field.
212    ///
213    /// Use this when the id column has been added via [`Self::add_column`]
214    /// (so its type and aliases were chosen explicitly) and you only need
215    /// to flag it. [`Self::with_id_column`] is the typed shortcut that
216    /// creates the column for you.
217    pub fn set_id_field(&mut self, name: impl Into<String>) {
218        self.id_field = Some(name.into());
219    }
220
221    /// Mark an already-added column as a display title.
222    ///
223    /// Companion to [`Self::set_id_field`] for spec-driven construction.
224    pub fn add_title_field(&mut self, name: impl Into<String>) {
225        let name = name.into();
226        if !self.title_fields.contains(&name) {
227            self.title_fields.push(name.clone());
228        }
229        if self.title_field.is_none() {
230            self.title_field = Some(name);
231        }
232    }
233
234    /// Get the current pagination configuration, if set
235    pub fn pagination(&self) -> Option<&Pagination> {
236        self.pagination.as_ref()
237    }
238
239    /// Column values every row in this set must hold (see the `invariants`
240    /// field): enforced on write — filled when null/absent, kept when matching,
241    /// rejected when conflicting.
242    pub fn invariants(&self) -> &IndexMap<String, T::Value> {
243        &self.invariants
244    }
245
246    /// Register an invariant value for `column` on this set.
247    ///
248    /// A later call for the same column overwrites the earlier invariant.
249    pub fn add_invariant(&mut self, column: impl Into<String>, value: T::Value) {
250        self.invariants.insert(column.into(), value);
251    }
252
253    /// Builder form of [`Self::add_invariant`].
254    pub fn with_invariant(mut self, column: impl Into<String>, value: T::Value) -> Self {
255        self.add_invariant(column, value);
256        self
257    }
258}
259
260impl<T: TableSource, E: Entity<T::Value>> std::ops::Index<&str> for Table<T, E> {
261    type Output = T::Column<T::AnyType>;
262
263    fn index(&self, index: &str) -> &Self::Output {
264        &self.columns[index]
265    }
266}
267
268impl<T: TableSource, E: Entity<T::Value>> std::fmt::Debug for Table<T, E> {
269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270        f.debug_struct("Table")
271            .field("table_name", &self.table_name())
272            .field("columns", &self.columns.keys().collect::<Vec<_>>())
273            .field("conditions_count", &self.conditions.len())
274            .field(
275                "refs_count",
276                &self.refs.as_ref().map(|r| r.len()).unwrap_or(0),
277            )
278            .field("expressions_count", &self.expressions.len())
279            .finish()
280    }
281}