vantage_table/table/impls/
refereces.rs1use ciborium::Value as CborValue;
4use indexmap::IndexMap;
5use std::sync::Arc;
6
7use vantage_core::{Result, error};
8use vantage_dataset::WritableValueSet;
9use vantage_expressions::Expression;
10use vantage_types::{EmptyEntity, Entity, Record};
11
12use crate::{
13 column::flags::ColumnFlag,
14 references::{ContainedRelation, HasMany, HasOne, Reference},
15 table::Table,
16 traits::{column_like::ColumnLike, table_source::TableSource},
17};
18
19impl<T: TableSource + 'static, E: Entity<T::Value> + 'static> Table<T, E> {
20 pub fn with_one<E2: Entity<T::Value> + 'static>(
26 mut self,
27 relation: &str,
28 foreign_key: &str,
29 build_target: impl Fn(T) -> Table<T, E2> + Send + Sync + 'static,
30 ) -> Self
31 where
32 T::Value: Into<ciborium::Value> + From<ciborium::Value>,
33 T::Id: std::fmt::Display + From<String>,
34 {
35 let reference = HasOne::<T, E, E2>::new(foreign_key, build_target);
36 self.add_ref(relation, Box::new(reference));
37 self
38 }
39
40 pub fn with_many<E2: Entity<T::Value> + 'static>(
46 mut self,
47 relation: &str,
48 foreign_key: &str,
49 build_target: impl Fn(T) -> Table<T, E2> + Send + Sync + 'static,
50 ) -> Self
51 where
52 T::Value: Into<ciborium::Value> + From<ciborium::Value>,
53 T::Id: std::fmt::Display + From<String>,
54 {
55 let reference = HasMany::<T, E, E2>::new(foreign_key, build_target);
56 self.add_ref(relation, Box::new(reference));
57 self
58 }
59
60 pub fn with_contained_one(
71 mut self,
72 relation: &str,
73 host_column: &str,
74 build_target: impl Fn(T) -> Table<T, EmptyEntity> + Send + Sync + 'static,
75 ) -> Self {
76 self.contained.push(ContainedRelation::new(
77 relation,
78 host_column,
79 vantage_vista::ContainedKind::ContainsOne,
80 None,
81 build_target,
82 ));
83 self
84 }
85
86 pub fn with_contained_many(
99 mut self,
100 relation: &str,
101 host_column: &str,
102 build_target: impl Fn(T) -> Table<T, EmptyEntity> + Send + Sync + 'static,
103 id_column: Option<&str>,
104 ) -> Self {
105 self.contained.push(ContainedRelation::new(
106 relation,
107 host_column,
108 vantage_vista::ContainedKind::ContainsMany,
109 id_column.map(str::to_string),
110 build_target,
111 ));
112 self
113 }
114
115 pub fn vista_columns(&self) -> Vec<vantage_vista::Column>
118 where
119 T::Column<T::AnyType>: ColumnLike<T::AnyType>,
120 {
121 self.columns()
122 .iter()
123 .map(|(name, col)| {
124 let mut vc = vantage_vista::Column::new(name.clone(), col.get_type().to_string());
125 if col.flags().contains(&ColumnFlag::Hidden) {
126 vc = vc.hidden();
127 }
128 vc
129 })
130 .collect()
131 }
132
133 #[allow(clippy::too_many_arguments)]
146 pub fn get_contained_ref(
147 &self,
148 relation: &str,
149 row: &Record<CborValue>,
150 parent_id: T::Id,
151 wrap: impl Fn(Table<T, EmptyEntity>) -> Result<vantage_vista::Vista> + Send + Sync + 'static,
152 decode_host: impl Fn(&CborValue) -> Option<CborValue>,
153 encode_host: impl Fn(CborValue) -> CborValue + Send + Sync + 'static,
154 ) -> Result<vantage_vista::Vista>
155 where
156 T::Value: From<CborValue> + Send + Sync,
157 T::Id: Clone + Send + Sync,
158 T::Column<T::AnyType>: ColumnLike<T::AnyType>,
159 {
160 let rel = self
161 .contained_relation(relation)
162 .ok_or_else(|| error!("unknown contained relation", relation = relation))?;
163 let host_value = row.get(rel.host_column()).and_then(decode_host);
164
165 let contained_table = rel.build_target(self.data_source().clone());
166 let mut spec = vantage_vista::ContainedSpec::new(rel.name(), rel.host_column(), rel.kind());
167 if let Some(id) = rel.id_column() {
168 spec = spec.with_id_column(id);
169 }
170 spec = spec.with_columns(contained_table.vista_columns());
171
172 let host_column = rel.host_column().to_string();
173 let parent_table = self.clone();
174 let writeback: vantage_vista::ContainedWriteback =
175 Arc::new(move |collection: CborValue| {
176 let parent_table = parent_table.clone();
177 let host_column = host_column.clone();
178 let parent_id = parent_id.clone();
179 let value = T::Value::from(encode_host(collection));
180 Box::pin(async move {
181 let mut patch: Record<T::Value> = Record::new();
182 patch.insert(host_column, value);
183 parent_table.patch_value(&parent_id, &patch).await?;
184 Ok(())
185 })
186 });
187
188 let ref_resolver: vantage_vista::ContainedRefResolver =
189 Arc::new(move |relation: &str, child_row: &Record<CborValue>| {
190 let native: Record<T::Value> = child_row
191 .iter()
192 .map(|(k, v)| (k.clone(), T::Value::from(v.clone())))
193 .collect();
194 let target = contained_table.get_ref_from_row::<EmptyEntity>(relation, &native)?;
195 wrap(target)
196 });
197
198 vantage_vista::build_contained_vista(
199 &spec,
200 host_value.as_ref(),
201 writeback,
202 Some(ref_resolver),
203 )
204 }
205
206 pub fn with_contained_specs<C>(
211 mut self,
212 specs: &IndexMap<String, vantage_vista::ContainedYaml<C>>,
213 build_col: impl Fn(&str, &vantage_vista::ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
214 ) -> Result<Self>
215 where
216 T::Column<T::AnyType>: Clone,
217 {
218 for (relation, c) in specs {
219 let cols = c
220 .columns
221 .iter()
222 .map(|(n, cs)| build_col(n, cs))
223 .collect::<Result<Vec<_>>>()?;
224 let rel = relation.clone();
225 let host = c.host_column.clone();
226 let build = move |db: T| {
227 let mut t = Table::<T, EmptyEntity>::new(rel.clone(), db);
228 for col in &cols {
229 t.add_column(col.clone());
230 }
231 t
232 };
233 self = match c.kind {
234 vantage_vista::ContainedKind::ContainsOne => {
235 self.with_contained_one(relation, &host, build)
236 }
237 vantage_vista::ContainedKind::ContainsMany => {
238 self.with_contained_many(relation, &host, build, c.id_column.as_deref())
239 }
240 };
241 }
242 Ok(self)
243 }
244
245 pub(crate) fn add_ref(&mut self, relation: &str, reference: Box<dyn Reference>) {
246 if self.refs.is_none() {
247 self.refs = Some(IndexMap::new());
248 }
249 self.refs
250 .as_mut()
251 .unwrap()
252 .insert(relation.to_string(), Arc::from(reference));
253 }
254
255 pub fn references(&self) -> Vec<String> {
256 self.refs
257 .as_ref()
258 .map(|refs| refs.keys().cloned().collect())
259 .unwrap_or_default()
260 }
261
262 pub fn with_id(mut self, id: impl Into<T::Value>) -> Result<Self> {
269 let id_name = self
270 .id_field()
271 .ok_or_else(|| error!("id field not set on table"))?
272 .name()
273 .to_string();
274 let condition = self.data_source().eq_value_condition(&id_name, id.into())?;
275 self.add_condition(condition);
276 Ok(self)
277 }
278
279 pub fn get_ref_from_row<E2: Entity<T::Value> + 'static>(
293 &self,
294 relation: &str,
295 row: &Record<T::Value>,
296 ) -> Result<Table<T, E2>> {
297 let (reference, _) = self.lookup_ref(relation)?;
298 let source_id = self
299 .id_field()
300 .map(|c| c.name().to_string())
301 .unwrap_or_else(|| "id".to_string());
302
303 let target_dyn = reference.resolve_from_row(
304 self.data_source() as &dyn std::any::Any,
305 &source_id,
306 row as &dyn std::any::Any,
307 )?;
308
309 let target_empty: Table<T, EmptyEntity> =
310 *target_dyn
311 .downcast::<Table<T, EmptyEntity>>()
312 .map_err(|_| error!("Failed to downcast target table to Table<T, EmptyEntity>"))?;
313
314 Ok(target_empty.into_entity::<E2>())
315 }
316
317 pub fn get_ref_as<E2: Entity<T::Value> + 'static>(
327 &self,
328 relation: &str,
329 ) -> Result<Table<T, E2>> {
330 let (reference, relation_str) = self.lookup_ref(relation)?;
331
332 let source_id = self
333 .id_field()
334 .map(|c| c.name().to_string())
335 .unwrap_or_else(|| "id".to_string());
336
337 let mut target: Table<T, E2> = *reference
338 .build_target(self.data_source() as &dyn std::any::Any)
339 .downcast::<Table<T, E2>>()
340 .map_err(|_| {
341 error!(
342 "Failed to downcast related table",
343 relation = relation_str.as_str()
344 )
345 })?;
346
347 let target_id = target
348 .id_field()
349 .map(|c| c.name().to_string())
350 .unwrap_or_else(|| "id".to_string());
351
352 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
353
354 let condition = self
355 .data_source()
356 .related_in_condition(&tgt_col, self, &src_col);
357 target.add_condition(condition);
358
359 Ok(target)
360 }
361
362 pub fn get_subquery_as<E2: Entity<T::Value> + 'static>(
369 &self,
370 relation: &str,
371 ) -> Result<Table<T, E2>> {
372 let (reference, relation_str) = self.lookup_ref(relation)?;
373
374 let source_id = self
375 .id_field()
376 .map(|c| c.name().to_string())
377 .unwrap_or_else(|| "id".to_string());
378
379 let mut target: Table<T, E2> = *reference
380 .build_target(self.data_source() as &dyn std::any::Any)
381 .downcast::<Table<T, E2>>()
382 .map_err(|_| {
383 error!(
384 "Failed to downcast related table",
385 relation = relation_str.as_str()
386 )
387 })?;
388
389 let target_id = target
390 .id_field()
391 .map(|c| c.name().to_string())
392 .unwrap_or_else(|| "id".to_string());
393
394 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
395
396 let condition = self.data_source().related_correlated_condition(
397 target.table_name(),
398 &tgt_col,
399 self.table_name(),
400 &src_col,
401 );
402 target.add_condition(condition);
403
404 Ok(target)
405 }
406
407 pub fn get_ref_target<E2: Entity<T::Value> + 'static>(
415 &self,
416 relation: &str,
417 ) -> Result<Table<T, E2>> {
418 let (reference, relation_str) = self.lookup_ref(relation)?;
419 let target: Table<T, E2> = *reference
420 .build_target(self.data_source() as &dyn std::any::Any)
421 .downcast::<Table<T, E2>>()
422 .map_err(|_| {
423 error!(
424 "Failed to downcast related table",
425 relation = relation_str.as_str()
426 )
427 })?;
428 Ok(target)
429 }
430
431 pub fn with_expression(
436 mut self,
437 name: &str,
438 expr_fn: impl Fn(&Table<T, E>) -> Expression<T::Value> + Send + Sync + 'static,
439 ) -> Self {
440 self.expressions.insert(name.to_string(), Arc::new(expr_fn));
441 self
442 }
443
444 fn lookup_ref(&self, relation: &str) -> Result<(&dyn Reference, String)> {
445 let table_name = self.table_name().to_string();
446 let refs = self.refs.as_ref().ok_or_else(|| {
447 error!(
448 "No references defined on table",
449 table = table_name.as_str()
450 )
451 })?;
452
453 let relation_str = relation.to_string();
454 let reference = refs.get(relation).ok_or_else(|| {
455 error!(
456 "Reference not found on table",
457 relation = relation_str.as_str(),
458 table = table_name.as_str()
459 )
460 })?;
461
462 Ok((reference.as_ref(), relation_str))
463 }
464
465 pub fn ref_cardinality(&self, relation: &str) -> Result<vantage_vista::ReferenceKind> {
467 let (reference, _) = self.lookup_ref(relation)?;
468 Ok(reference.cardinality())
469 }
470
471 pub fn ref_kinds(&self) -> Vec<(String, vantage_vista::ReferenceKind)> {
473 self.refs
474 .as_ref()
475 .map(|refs| {
476 refs.iter()
477 .map(|(name, r)| (name.clone(), r.cardinality()))
478 .collect()
479 })
480 .unwrap_or_default()
481 }
482}