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 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 self.add_ref_arc(relation, Arc::from(reference));
247 }
248
249 pub(crate) fn add_ref_arc(&mut self, relation: &str, reference: Arc<dyn Reference>) {
252 self.refs
253 .get_or_insert_with(IndexMap::new)
254 .insert(relation.to_string(), reference);
255 }
256
257 pub(crate) fn refs_ref(&self) -> Option<&IndexMap<String, Arc<dyn Reference>>> {
259 self.refs.as_ref()
260 }
261
262 pub fn copy_relations_from<E2: Entity<T::Value> + 'static>(
267 &mut self,
268 other: &Table<T, E2>,
269 names: Option<&[&str]>,
270 ) {
271 let Some(refs) = other.refs_ref() else {
272 return;
273 };
274 for (name, reference) in refs {
275 if names.is_some_and(|ns| !ns.contains(&name.as_str())) {
276 continue;
277 }
278 self.add_ref_arc(name, reference.clone());
279 }
280 }
281
282 pub fn references(&self) -> Vec<String> {
283 self.refs
284 .as_ref()
285 .map(|refs| refs.keys().cloned().collect())
286 .unwrap_or_default()
287 }
288
289 pub fn with_id(mut self, id: impl Into<T::Value>) -> Result<Self> {
296 let id_name = self
297 .id_field()
298 .ok_or_else(|| error!("id field not set on table"))?
299 .name()
300 .to_string();
301 let condition = self.data_source().eq_value_condition(&id_name, id.into())?;
302 self.add_condition(condition);
303 Ok(self)
304 }
305
306 pub fn get_ref_from_row<E2: Entity<T::Value> + 'static>(
320 &self,
321 relation: &str,
322 row: &Record<T::Value>,
323 ) -> Result<Table<T, E2>> {
324 let (reference, _) = self.lookup_ref(relation)?;
325 let source_id = self
326 .id_field()
327 .map(|c| c.name().to_string())
328 .unwrap_or_else(|| "id".to_string());
329
330 let target_dyn = reference.resolve_from_row(
331 self.data_source() as &dyn std::any::Any,
332 &source_id,
333 row as &dyn std::any::Any,
334 )?;
335
336 let target_empty: Table<T, EmptyEntity> =
337 *target_dyn
338 .downcast::<Table<T, EmptyEntity>>()
339 .map_err(|_| error!("Failed to downcast target table to Table<T, EmptyEntity>"))?;
340
341 Ok(target_empty.into_entity::<E2>())
342 }
343
344 pub fn get_ref_as<E2: Entity<T::Value> + 'static>(
354 &self,
355 relation: &str,
356 ) -> Result<Table<T, E2>> {
357 let (reference, relation_str) = self.lookup_ref(relation)?;
358
359 let source_id = self
360 .id_field()
361 .map(|c| c.name().to_string())
362 .unwrap_or_else(|| "id".to_string());
363
364 let mut target: Table<T, E2> = *reference
365 .build_target(self.data_source() as &dyn std::any::Any)
366 .downcast::<Table<T, E2>>()
367 .map_err(|_| {
368 error!(
369 "Failed to downcast related table",
370 relation = relation_str.as_str()
371 )
372 })?;
373
374 let target_id = target
375 .id_field()
376 .map(|c| c.name().to_string())
377 .unwrap_or_else(|| "id".to_string());
378
379 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
380
381 let condition = self
382 .data_source()
383 .related_in_condition(&tgt_col, self, &src_col);
384 target.add_condition(condition);
385
386 Ok(target)
387 }
388
389 pub fn get_subquery_as<E2: Entity<T::Value> + 'static>(
396 &self,
397 relation: &str,
398 ) -> Result<Table<T, E2>> {
399 let (reference, relation_str) = self.lookup_ref(relation)?;
400
401 let source_id = self
402 .id_field()
403 .map(|c| c.name().to_string())
404 .unwrap_or_else(|| "id".to_string());
405
406 let mut target: Table<T, E2> = *reference
407 .build_target(self.data_source() as &dyn std::any::Any)
408 .downcast::<Table<T, E2>>()
409 .map_err(|_| {
410 error!(
411 "Failed to downcast related table",
412 relation = relation_str.as_str()
413 )
414 })?;
415
416 let target_id = target
417 .id_field()
418 .map(|c| c.name().to_string())
419 .unwrap_or_else(|| "id".to_string());
420
421 let (src_col, tgt_col) = reference.columns(&source_id, &target_id);
422
423 let condition = self.data_source().related_correlated_condition(
424 target.table_name(),
425 &tgt_col,
426 self.table_name(),
427 &src_col,
428 );
429 target.add_condition(condition);
430
431 Ok(target)
432 }
433
434 pub fn get_ref_target<E2: Entity<T::Value> + 'static>(
442 &self,
443 relation: &str,
444 ) -> Result<Table<T, E2>> {
445 let (reference, relation_str) = self.lookup_ref(relation)?;
446 let target: Table<T, E2> = *reference
447 .build_target(self.data_source() as &dyn std::any::Any)
448 .downcast::<Table<T, E2>>()
449 .map_err(|_| {
450 error!(
451 "Failed to downcast related table",
452 relation = relation_str.as_str()
453 )
454 })?;
455 Ok(target)
456 }
457
458 pub fn with_expression(
463 mut self,
464 name: &str,
465 expr_fn: impl Fn(&Table<T, E>) -> Expression<T::Value> + Send + Sync + 'static,
466 ) -> Self {
467 self.expressions.insert(name.to_string(), Arc::new(expr_fn));
468 self
469 }
470
471 fn lookup_ref(&self, relation: &str) -> Result<(&dyn Reference, String)> {
472 let table_name = self.table_name().to_string();
473 let refs = self.refs.as_ref().ok_or_else(|| {
474 error!(
475 "No references defined on table",
476 table = table_name.as_str()
477 )
478 })?;
479
480 let relation_str = relation.to_string();
481 let reference = refs.get(relation).ok_or_else(|| {
482 error!(
483 "Reference not found on table",
484 relation = relation_str.as_str(),
485 table = table_name.as_str()
486 )
487 })?;
488
489 Ok((reference.as_ref(), relation_str))
490 }
491
492 pub fn ref_cardinality(&self, relation: &str) -> Result<vantage_vista::ReferenceKind> {
494 let (reference, _) = self.lookup_ref(relation)?;
495 Ok(reference.cardinality())
496 }
497
498 pub fn ref_kinds(&self) -> Vec<(String, vantage_vista::ReferenceKind)> {
500 self.refs
501 .as_ref()
502 .map(|refs| {
503 refs.iter()
504 .map(|(name, r)| (name.clone(), r.cardinality()))
505 .collect()
506 })
507 .unwrap_or_default()
508 }
509}