1use 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 if self.imported_columns.contains(name) {
132 vc = vc.with_flag(vantage_vista::flags::CALCULATED);
133 }
134 vc
135 })
136 .collect()
137 }
138
139 #[allow(clippy::too_many_arguments)]
152 pub fn get_contained_ref(
153 &self,
154 relation: &str,
155 row: &Record<CborValue>,
156 parent_id: T::Id,
157 wrap: impl Fn(Table<T, EmptyEntity>) -> Result<vantage_vista::Vista> + Send + Sync + 'static,
158 decode_host: impl Fn(&CborValue) -> Option<CborValue>,
159 encode_host: impl Fn(CborValue) -> CborValue + Send + Sync + 'static,
160 ) -> Result<vantage_vista::Vista>
161 where
162 T::Value: From<CborValue> + Send + Sync + vantage_types::InvariantValue,
163 T::Id: Clone + Send + Sync,
164 T::Column<T::AnyType>: ColumnLike<T::AnyType>,
165 {
166 let rel = self
167 .contained_relation(relation)
168 .ok_or_else(|| error!("unknown contained relation", relation = relation))?;
169 let host_value = row.get(rel.host_column()).and_then(decode_host);
170
171 let contained_table = rel.build_target(self.data_source().clone());
172 let mut spec = vantage_vista::ContainedSpec::new(rel.name(), rel.host_column(), rel.kind());
173 if let Some(id) = rel.id_column() {
174 spec = spec.with_id_column(id);
175 }
176 spec = spec.with_columns(contained_table.vista_columns());
177
178 let host_column = rel.host_column().to_string();
179 let parent_table = self.clone();
180 let writeback: vantage_vista::ContainedWriteback =
181 Arc::new(move |collection: CborValue| {
182 let parent_table = parent_table.clone();
183 let host_column = host_column.clone();
184 let parent_id = parent_id.clone();
185 let value = T::Value::from(encode_host(collection));
186 Box::pin(async move {
187 let mut patch: Record<T::Value> = Record::new();
188 patch.insert(host_column, value);
189 parent_table.patch_value(parent_id.clone(), &patch).await?;
190 Ok(())
191 })
192 });
193
194 let ref_resolver: vantage_vista::ContainedRefResolver =
195 Arc::new(move |relation: &str, child_row: &Record<CborValue>| {
196 let native: Record<T::Value> = child_row
197 .iter()
198 .map(|(k, v)| (k.clone(), T::Value::from(v.clone())))
199 .collect();
200 let target = contained_table.get_ref_from_row::<EmptyEntity>(relation, &native)?;
201 wrap(target)
202 });
203
204 vantage_vista::build_contained_vista(
205 &spec,
206 host_value.as_ref(),
207 writeback,
208 Some(ref_resolver),
209 )
210 }
211
212 pub fn with_contained_specs<C>(
217 mut self,
218 specs: &IndexMap<String, vantage_vista::ContainedYaml<C>>,
219 build_col: impl Fn(&str, &vantage_vista::ColumnSpec<C>) -> Result<T::Column<T::AnyType>>,
220 ) -> Result<Self>
221 where
222 T::Column<T::AnyType>: Clone,
223 {
224 for (relation, c) in specs {
225 let cols = c
226 .columns
227 .iter()
228 .map(|(n, cs)| build_col(n, cs))
229 .collect::<Result<Vec<_>>>()?;
230 let rel = relation.clone();
231 let host = c.host_column.clone();
232 let build = move |db: T| {
233 let mut t = Table::<T, EmptyEntity>::new(rel.clone(), db);
234 for col in &cols {
235 t.add_column(col.clone());
236 }
237 t
238 };
239 self = match c.kind {
240 vantage_vista::ContainedKind::ContainsOne => {
241 self.with_contained_one(relation, &host, build)
242 }
243 vantage_vista::ContainedKind::ContainsMany => {
244 self.with_contained_many(relation, &host, build, c.id_column.as_deref())
245 }
246 };
247 }
248 Ok(self)
249 }
250
251 pub(crate) fn add_ref(&mut self, relation: &str, reference: Box<dyn Reference>) {
252 self.add_ref_arc(relation, Arc::from(reference));
253 }
254
255 pub(crate) fn add_ref_arc(&mut self, relation: &str, reference: Arc<dyn Reference>) {
258 self.refs
259 .get_or_insert_with(IndexMap::new)
260 .insert(relation.to_string(), reference);
261 }
262
263 pub(crate) fn refs_ref(&self) -> Option<&IndexMap<String, Arc<dyn Reference>>> {
265 self.refs.as_ref()
266 }
267
268 pub fn copy_relations_from<E2: Entity<T::Value> + 'static>(
273 &mut self,
274 other: &Table<T, E2>,
275 names: Option<&[&str]>,
276 ) {
277 let Some(refs) = other.refs_ref() else {
278 return;
279 };
280 for (name, reference) in refs {
281 if names.is_some_and(|ns| !ns.contains(&name.as_str())) {
282 continue;
283 }
284 self.add_ref_arc(name, reference.clone());
285 }
286 }
287
288 pub fn references(&self) -> Vec<String> {
289 self.refs
290 .as_ref()
291 .map(|refs| refs.keys().cloned().collect())
292 .unwrap_or_default()
293 }
294
295 pub fn with_id(mut self, id: impl Into<T::Value>) -> Result<Self> {
302 let id_name = self
303 .id_field()
304 .ok_or_else(|| error!("id field not set on table"))?
305 .name()
306 .to_string();
307 let id = id.into();
308 let condition = self
309 .data_source()
310 .eq_value_condition(&id_name, id.clone())?;
311 self.add_condition(condition);
312 self.add_invariant(id_name, id);
314 Ok(self)
315 }
316
317 pub fn get_ref_from_row<E2: Entity<T::Value> + 'static>(
331 &self,
332 relation: &str,
333 row: &Record<T::Value>,
334 ) -> Result<Table<T, E2>> {
335 let (reference, _) = self.lookup_ref(relation)?;
336 let source_id = self
337 .id_field()
338 .map(|c| c.name().to_string())
339 .unwrap_or_else(|| "id".to_string());
340
341 let target_dyn = reference.resolve_from_row(
342 self.data_source() as &dyn std::any::Any,
343 &source_id,
344 row as &dyn std::any::Any,
345 )?;
346
347 let target_empty: Table<T, EmptyEntity> =
348 *target_dyn
349 .downcast::<Table<T, EmptyEntity>>()
350 .map_err(|_| error!("Failed to downcast target table to Table<T, EmptyEntity>"))?;
351
352 Ok(target_empty.into_entity::<E2>())
353 }
354
355 pub fn get_ref_as<E2: Entity<T::Value> + 'static>(
365 &self,
366 relation: &str,
367 ) -> Result<Table<T, E2>> {
368 let (reference, relation_str) = self.lookup_ref(relation)?;
369
370 let source_id = self
371 .id_field()
372 .map(|c| c.name().to_string())
373 .unwrap_or_else(|| "id".to_string());
374
375 let mut target: Table<T, E2> = *reference
376 .build_target(self.data_source() as &dyn std::any::Any)
377 .downcast::<Table<T, E2>>()
378 .map_err(|_| {
379 error!(
380 "Failed to downcast related table",
381 relation = relation_str.as_str()
382 )
383 })?;
384
385 let target_id = target
386 .id_field()
387 .map(|c| c.name().to_string())
388 .unwrap_or_else(|| "id".to_string());
389
390 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
391
392 let condition = self
393 .data_source()
394 .related_in_condition(&tgt_col, self, &src_col);
395 target.add_condition(condition);
396
397 Ok(target)
398 }
399
400 pub fn get_subquery_as<E2: Entity<T::Value> + 'static>(
407 &self,
408 relation: &str,
409 ) -> Result<Table<T, E2>> {
410 let (reference, relation_str) = self.lookup_ref(relation)?;
411 let mut target: Table<T, E2> = *reference
412 .build_target(self.data_source() as &dyn std::any::Any)
413 .downcast::<Table<T, E2>>()
414 .map_err(|_| {
415 error!(
416 "Failed to downcast related table",
417 relation = relation_str.as_str()
418 )
419 })?;
420 self.attach_correlated_condition(reference, &mut target);
421 Ok(target)
422 }
423
424 pub fn get_subquery_erased(&self, relation: &str) -> Result<Table<T, EmptyEntity>> {
429 let (reference, relation_str) = self.lookup_ref(relation)?;
430 let mut target: Table<T, EmptyEntity> = *reference
431 .build_target_erased(self.data_source() as &dyn std::any::Any)
432 .downcast::<Table<T, EmptyEntity>>()
433 .map_err(|_| {
434 error!(
435 "Failed to downcast related table",
436 relation = relation_str.as_str()
437 )
438 })?;
439 self.attach_correlated_condition(reference, &mut target);
440 Ok(target)
441 }
442
443 fn attach_correlated_condition<E2: Entity<T::Value> + 'static>(
448 &self,
449 reference: &dyn Reference,
450 target: &mut Table<T, E2>,
451 ) {
452 let source_id = self
453 .id_field()
454 .map(|c| c.name().to_string())
455 .unwrap_or_else(|| "id".to_string());
456 let target_id = target
457 .id_field()
458 .map(|c| c.name().to_string())
459 .unwrap_or_else(|| "id".to_string());
460 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
461 let condition = self.data_source().related_correlated_condition(
462 target.table_name(),
463 &tgt_col,
464 self.table_name(),
465 &src_col,
466 );
467 target.add_condition(condition);
468 }
469
470 pub fn get_ref_target_erased(&self, relation: &str) -> Result<Table<T, EmptyEntity>> {
474 let (reference, relation_str) = self.lookup_ref(relation)?;
475 let target: Table<T, EmptyEntity> = *reference
476 .build_target_erased(self.data_source() as &dyn std::any::Any)
477 .downcast::<Table<T, EmptyEntity>>()
478 .map_err(|_| {
479 error!(
480 "Failed to downcast related table",
481 relation = relation_str.as_str()
482 )
483 })?;
484 Ok(target)
485 }
486
487 pub fn get_ref_target<E2: Entity<T::Value> + 'static>(
495 &self,
496 relation: &str,
497 ) -> Result<Table<T, E2>> {
498 let (reference, relation_str) = self.lookup_ref(relation)?;
499 let target: Table<T, E2> = *reference
500 .build_target(self.data_source() as &dyn std::any::Any)
501 .downcast::<Table<T, E2>>()
502 .map_err(|_| {
503 error!(
504 "Failed to downcast related table",
505 relation = relation_str.as_str()
506 )
507 })?;
508 Ok(target)
509 }
510
511 pub fn with_expression(
516 mut self,
517 name: &str,
518 expr_fn: impl Fn(&Table<T, E>) -> Expression<T::Value> + Send + Sync + 'static,
519 ) -> Self
520 where
521 T: 'static,
522 E: 'static,
523 {
524 let wrapped: crate::table::base::ExpressionFn<T> =
528 Arc::new(move |erased: &Table<T, vantage_types::EmptyEntity>| {
529 let typed: &Table<T, E> = unsafe {
531 &*(erased as *const Table<T, vantage_types::EmptyEntity> as *const Table<T, E>)
532 };
533 expr_fn(typed)
534 });
535 self.expressions.insert(name.to_string(), wrapped);
536 self
537 }
538
539 pub fn with_lazy_expression<F, Fut>(mut self, name: &str, f: F) -> Self
558 where
559 F: Fn(&vantage_types::Record<T::Value>) -> Fut + Send + Sync + 'static,
560 Fut: std::future::Future<Output = Result<T::Value>> + Send + 'static,
561 {
562 if !self.columns.contains_key(name) {
563 let column = self.data_source.create_column::<T::AnyType>(name);
564 self.add_column(column);
565 }
566 self.add_lazy_expression(name, Arc::new(move |record| Box::pin(f(record))));
567 self
568 }
569
570 pub fn add_lazy_expression(&mut self, name: &str, f: crate::table::base::LazyExpressionFn<T>) {
575 self.lazy_expressions.insert(name.to_string(), f);
576 }
577
578 fn lookup_ref(&self, relation: &str) -> Result<(&dyn Reference, String)> {
579 let table_name = self.table_name().to_string();
580 let refs = self.refs.as_ref().ok_or_else(|| {
581 error!(
582 "No references defined on table",
583 table = table_name.as_str()
584 )
585 })?;
586
587 let relation_str = relation.to_string();
588 let reference = refs.get(relation).ok_or_else(|| {
589 error!(
590 "Reference not found on table",
591 relation = relation_str.as_str(),
592 table = table_name.as_str()
593 )
594 })?;
595
596 Ok((reference.as_ref(), relation_str))
597 }
598
599 pub fn ref_cardinality(&self, relation: &str) -> Result<vantage_vista::ReferenceKind> {
601 let (reference, _) = self.lookup_ref(relation)?;
602 Ok(reference.cardinality())
603 }
604
605 pub fn ref_foreign_key(&self, relation: &str) -> Result<String> {
610 let (reference, _) = self.lookup_ref(relation)?;
611 Ok(reference.foreign_key().to_string())
612 }
613
614 pub fn ref_kinds(&self) -> Vec<(String, vantage_vista::ReferenceKind)> {
616 self.refs
617 .as_ref()
618 .map(|refs| {
619 refs.iter()
620 .map(|(name, r)| (name.clone(), r.cardinality()))
621 .collect()
622 })
623 .unwrap_or_default()
624 }
625}