1use indexmap::IndexMap;
6use vantage_core::{Context, Result, VantageError, error};
7use vantage_expressions::{Expressive, SelectableDataSource};
8use vantage_types::{EmptyEntity, Entity};
9use vantage_vista::{ColumnFlag, ColumnSpec, JoinKey, ReferenceKind, ReferenceSugar, VistaSpec};
10
11use crate::{
12 source::SelectSource,
13 table::Table,
14 table::inject::{BuildEnv, BuildKind, BuildTrail, SpecTableBuilder},
15 traits::table_source::TableSource,
16};
17
18impl<T: TableSource, E: Entity<T::Value>> Table<T, E> {
19 pub fn add_spec_column<C>(
24 &mut self,
25 name: &str,
26 spec: &ColumnSpec<C>,
27 build: impl FnOnce(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
28 ) -> Result<bool> {
29 if self.add_lazy_spec_column(spec, name)? {
30 return Ok(false);
31 }
32 if !self.columns.contains_key(name) {
33 self.add_column(build(name, spec)?);
34 }
35 if spec.has_flag(ColumnFlag::Title) {
36 self.add_title_field(name);
37 }
38 Ok(true)
39 }
40
41 pub fn add_spec_columns<C>(
43 &mut self,
44 columns: &IndexMap<String, ColumnSpec<C>>,
45 build: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
46 ) -> Result<()> {
47 for (name, spec) in columns {
48 self.add_spec_column(name, spec, &build)?;
49 }
50 Ok(())
51 }
52
53 pub fn set_spec_id_field(&mut self, id: &str) -> Result<()> {
56 if !self.columns.contains_key(id) {
57 return Err(error!("id column not present in spec.columns", id = id));
58 }
59 self.set_id_field(id);
60 Ok(())
61 }
62}
63
64impl<T: TableSource + 'static, E: Entity<T::Value> + 'static> Table<T, E>
65where
66 T::Value: Into<ciborium::Value> + From<ciborium::Value>,
67 T::Id: std::fmt::Display + From<String>,
68{
69 pub fn with_spec_references<X, C, R, S: 'static>(
81 mut self,
82 spec: &VistaSpec<X, C, R>,
83 env: &BuildEnv<S, T>,
84 build: SpecTableBuilder<S, T>,
85 ) -> Result<Self> {
86 for (relation, reference) in &spec.references {
87 let foreign_key = reference.foreign_key(relation);
88 self = self.with_spec_join(
89 &spec.name,
90 relation,
91 &reference.table,
92 reference.kind,
93 &foreign_key,
94 env,
95 build,
96 )?;
97 }
98 for (column, sugar) in column_references(spec) {
99 let (target, kind, foreign_key) = match sugar {
100 ReferenceSugar::Sugar(target) => (target, ReferenceKind::HasOne, column.clone()),
101 ReferenceSugar::Full(r) => (&r.table, r.kind, r.foreign_key(column)),
102 _ => return Err(unknown_sugar(column)),
103 };
104 self =
105 self.with_spec_join(&spec.name, column, target, kind, &foreign_key, env, build)?;
106 }
107 Ok(self)
108 }
109
110 #[allow(clippy::too_many_arguments)]
111 fn with_spec_join<S: 'static>(
112 self,
113 from: &str,
114 relation: &str,
115 target: &str,
116 kind: ReferenceKind,
117 foreign_key: &str,
118 env: &BuildEnv<S, T>,
119 build: SpecTableBuilder<S, T>,
120 ) -> Result<Self> {
121 let target_name = target.to_string();
122 let relation_name = relation.to_string();
123 let child = BuildEnv {
124 resolver: env.resolver.clone(),
125 injector: env.injector.clone(),
126 trail: BuildTrail::default().enter(target)?,
127 kind: BuildKind::Reference {
128 from: from.to_string(),
129 relation: relation.to_string(),
130 },
131 };
132 let build_target = move |source: T| -> Result<Table<T, EmptyEntity>> {
133 let Some(r) = &child.resolver else {
134 let mut table = Table::new(target_name.clone(), source);
135 child.inject(&target_name, &mut table, build)?;
136 return Ok(table);
137 };
138 let spec = r.resolve(&target_name).ok_or_else(|| {
139 error!(
140 "reference target has no spec",
141 relation = relation_name.as_str(),
142 target = target_name.as_str()
143 )
144 })?;
145 build(&spec, source, &child).with_context(|| {
146 error!(
147 "reference target failed to build",
148 relation = relation_name.as_str(),
149 target = target_name.as_str()
150 )
151 })
152 };
153 match kind {
154 ReferenceKind::HasOne => {
155 Ok(self.try_with_one(relation, foreign_key, target, build_target))
156 }
157 ReferenceKind::HasMany => {
158 Ok(self.try_with_many(relation, foreign_key, target, build_target))
159 }
160 _ => Err(error!(
161 "references: entries must be has_one or has_many",
162 relation = relation
163 )),
164 }
165 }
166}
167
168pub fn spec_references<X, C, R>(
175 spec: &VistaSpec<X, C, R>,
176 source_id: &str,
177) -> Result<Vec<vantage_vista::Reference>> {
178 let mut out = Vec::with_capacity(spec.references.len());
179 for (relation, reference) in &spec.references {
180 out.push(reference.to_reference(relation, source_id)?);
181 }
182 for (column, sugar) in column_references(spec) {
183 out.push(match sugar {
184 ReferenceSugar::Sugar(target) => {
185 vantage_vista::Reference::has_one(column, target, JoinKey::to_target_id(column))
186 }
187 ReferenceSugar::Full(r) => r.to_reference(column, source_id)?,
188 _ => return Err(unknown_sugar(column)),
189 });
190 }
191 Ok(out)
192}
193
194fn unknown_sugar(column: &str) -> VantageError {
195 error!(
196 "column references: takes a table name or a full reference",
197 column = column
198 )
199}
200
201fn column_references<X, C, R>(
204 spec: &VistaSpec<X, C, R>,
205) -> impl Iterator<Item = (&String, &ReferenceSugar)> {
206 spec.columns.iter().filter_map(|(column, col)| {
207 let sugar = col.references.as_ref()?;
208 (!spec.references.contains_key(column)).then_some((column, sugar))
209 })
210}
211
212impl<T, E, V, Cond, S> Table<T, E>
214where
215 T: SelectableDataSource<V, Cond, Select = S>
216 + TableSource<Value = V, Condition = Cond, Source = SelectSource<S>>,
217 V: Clone + Send + Sync + 'static + From<String>,
218 Cond: Clone + Send + Sync + 'static,
219 S: Expressive<V> + Clone,
220 E: Entity<V>,
221{
222 pub fn derive_from_spec<E2: Entity<V> + 'static, X, C, R>(
227 base: &Table<T, E2>,
228 spec: &VistaSpec<X, C, R>,
229 select: S,
230 inherit_columns: &[String],
231 inherit_relations: &[String],
232 build_column: impl Fn(&str, &ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
233 ) -> Result<Self>
234 where
235 T: 'static,
236 E: 'static,
237 {
238 let columns: Vec<&str> = inherit_columns.iter().map(String::as_str).collect();
239 let relations: Vec<&str> = inherit_relations.iter().map(String::as_str).collect();
240 let mut table = Self::derive_from(
241 base,
242 spec.name.clone(),
243 move |_| select,
244 &columns,
245 &relations,
246 );
247 table.add_spec_columns(&spec.columns, &build_column)?;
248 if let Some(id) = &spec.id_column {
249 table.set_id_field(id);
250 }
251 table.with_contained_specs(&spec.contained, build_column)
252 }
253}