Skip to main content

vantage_table/table/impls/
spec.rs

1//! Lowering a YAML [`VistaSpec`] onto a table:
2//! the steps every driver factory shares. Type mapping and driver blocks stay
3//! with the driver, passed in as `build` functions.
4
5use indexmap::IndexMap;
6use vantage_core::{Result, error};
7use vantage_expressions::{Expressive, SelectableDataSource};
8use vantage_types::{EmptyEntity, Entity};
9use vantage_vista::{ColumnSpec, ReferenceKind, ReferenceSpec, SpecResolver, VistaSpec, flags};
10
11use crate::{source::SelectSource, table::Table, traits::table_source::TableSource};
12
13/// Rebuilds a referenced table from its spec, a data source and the resolver.
14type SpecTableBuilder<S, T> = fn(&S, T, Option<SpecResolver<S>>) -> Result<Table<T, EmptyEntity>>;
15
16impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
17    /// Add one spec column. A `lazy:` column becomes a computed column;
18    /// any other is built by `build` (unless the table already has it, as a
19    /// derived table may) and registered as a title when flagged `title`.
20    /// Returns whether the column is stored.
21    pub fn add_spec_column<C>(
22        &mut self,
23        name: &str,
24        spec: &ColumnSpec<C>,
25        build: impl FnOnce(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
26    ) -> Result<bool> {
27        if self.add_lazy_spec_column(spec, name)? {
28            return Ok(false);
29        }
30        if !self.columns.contains_key(name) {
31            self.add_column(build(name, spec)?);
32        }
33        if spec.has_flag(flags::TITLE) {
34            self.add_title_field(name);
35        }
36        Ok(true)
37    }
38
39    /// [`add_spec_column`](Self::add_spec_column) for each column, in order.
40    pub fn add_spec_columns<C>(
41        &mut self,
42        columns: &IndexMap<String, ColumnSpec<C>>,
43        build: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
44    ) -> Result<()> {
45        for (name, spec) in columns {
46            self.add_spec_column(name, spec, &build)?;
47        }
48        Ok(())
49    }
50
51    /// Mark the spec's id column as the id field. Errors when the spec
52    /// doesn't declare that column.
53    pub fn set_spec_id_field(&mut self, id: &str) -> Result<()> {
54        if !self.columns.contains_key(id) {
55            return Err(error!("id column not present in spec.columns", id = id));
56        }
57        self.set_id_field(id);
58        Ok(())
59    }
60}
61
62impl<T: TableSource + 'static, E: Entity<T::Value> + 'static> Table<T, E>
63where
64    T::Value: Into<ciborium::Value> + From<ciborium::Value>,
65    T::Id: std::fmt::Display + From<String>,
66{
67    /// Register each spec `references:` entry as `with_one` / `with_many`.
68    ///
69    /// At traversal time the reference asks `resolver` for the target's
70    /// current spec and rebuilds it with `build`. Without a resolver, or on a
71    /// miss or a build error, the target is a column-less `Table::new(target)`
72    /// and the next query against it fails.
73    pub fn with_spec_references<R, S: 'static>(
74        mut self,
75        references: &IndexMap<String, ReferenceSpec<R>>,
76        resolver: Option<SpecResolver<S>>,
77        build: SpecTableBuilder<S, T>,
78    ) -> Self {
79        for (relation, reference) in references {
80            let target = reference.table.clone();
81            let foreign_key = reference.foreign_key_or(relation);
82            let resolver = resolver.clone();
83            let build_target = move |source: T| -> Table<T, EmptyEntity> {
84                if let Some(r) = &resolver
85                    && let Some(spec) = r(&target)
86                    && let Ok(table) = build(&spec, source.clone(), Some(r.clone()))
87                {
88                    return table;
89                }
90                Table::new(target.clone(), source)
91            };
92            self = match reference.kind {
93                ReferenceKind::HasOne => self.with_one(relation, &foreign_key, build_target),
94                ReferenceKind::HasMany => self.with_many(relation, &foreign_key, build_target),
95            };
96        }
97        self
98    }
99}
100
101// Same bounds as `Table::derive_from`, which this builds on.
102impl<T, E, V, Cond, S> Table<T, E>
103where
104    T: SelectableDataSource<V, Cond, Select = S>
105        + TableSource<Value = V, Condition = Cond, Source = SelectSource<S>>,
106    V: Clone + Send + Sync + 'static + From<String>,
107    Cond: Clone + Send + Sync + 'static,
108    S: Expressive<V> + Clone,
109    E: Entity<V>,
110{
111    /// Lower a `base:` spec: a table over `select` (the base's select, possibly
112    /// transformed) that inherits the listed base columns and relations, adds
113    /// the spec's own columns (aggregate outputs, say) and contained
114    /// relations, and keeps the base's id unless the spec names one.
115    pub fn derive_from_spec<E2: Entity<V> + 'static, X, C, R>(
116        base: &Table<T, E2>,
117        spec: &VistaSpec<X, C, R>,
118        select: S,
119        inherit_columns: &[String],
120        inherit_relations: &[String],
121        build_column: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
122    ) -> Result<Self>
123    where
124        T: 'static,
125        E: 'static,
126    {
127        let columns: Vec<&str> = inherit_columns.iter().map(String::as_str).collect();
128        let relations: Vec<&str> = inherit_relations.iter().map(String::as_str).collect();
129        let mut table = Self::derive_from(
130            base,
131            spec.name.clone(),
132            move |_| select,
133            &columns,
134            &relations,
135        );
136        table.add_spec_columns(&spec.columns, &build_column)?;
137        if let Some(id) = &spec.id_column {
138            table.set_id_field(id);
139        }
140        table.with_contained_specs(&spec.contained, build_column)
141    }
142}