Skip to main content

vantage_table/references/
contained.rs

1//! Contained relation registration — a closure that builds the embedded
2//! record's table, mirroring how [`HasOne`](super::HasOne) /
3//! [`HasMany`](super::HasMany) carry a `build_target` for joined tables.
4//!
5//! The contained record's fields are typed in the parent's own type system (a
6//! line's `product` is the driver's `Thing`, its `quantity` an `i64`), so the
7//! closure has the same shape as `with_many`'s — `Fn(T) -> Table<T, E2>`. It's
8//! evaluated lazily at traversal time; the driver runs its normal
9//! column-harvesting routine on the result to derive the sub-Vista's schema.
10//! The returned table carries a real data source it is never queried through —
11//! only its column/relation schema is read; row data comes from the parent's
12//! embedded column.
13
14use std::sync::Arc;
15
16use vantage_types::EmptyEntity;
17use vantage_vista::{ContainedKind, ContainedSpec};
18
19use crate::{table::Table, traits::table_source::TableSource};
20
21/// A contained (embedded-in-row) relation declared on a [`Table`].
22pub struct ContainedRelation<T: TableSource> {
23    name: String,
24    host_column: String,
25    kind: ContainedKind,
26    id_column: Option<String>,
27    build_target: Arc<dyn Fn(T) -> Table<T, EmptyEntity> + Send + Sync>,
28}
29
30impl<T: TableSource> Clone for ContainedRelation<T> {
31    fn clone(&self) -> Self {
32        Self {
33            name: self.name.clone(),
34            host_column: self.host_column.clone(),
35            kind: self.kind,
36            id_column: self.id_column.clone(),
37            build_target: self.build_target.clone(),
38        }
39    }
40}
41
42impl<T: TableSource + 'static> ContainedRelation<T> {
43    pub fn new(
44        name: impl Into<String>,
45        host_column: impl Into<String>,
46        kind: ContainedKind,
47        id_column: Option<String>,
48        build_target: impl Fn(T) -> Table<T, EmptyEntity> + Send + Sync + 'static,
49    ) -> Self {
50        Self {
51            name: name.into(),
52            host_column: host_column.into(),
53            kind,
54            id_column,
55            build_target: Arc::new(build_target),
56        }
57    }
58
59    pub fn name(&self) -> &str {
60        &self.name
61    }
62
63    pub fn host_column(&self) -> &str {
64        &self.host_column
65    }
66
67    pub fn kind(&self) -> ContainedKind {
68        self.kind
69    }
70
71    pub fn id_column(&self) -> Option<&str> {
72        self.id_column.as_deref()
73    }
74
75    /// Build the contained record's table (its schema), used at traversal to
76    /// derive the sub-Vista's columns.
77    pub fn build_target(&self, db: T) -> Table<T, EmptyEntity> {
78        (self.build_target)(db)
79    }
80
81    /// Shape-only vista spec (name, host, kind, id — no columns) for metadata
82    /// surfacing. Columns are materialized at traversal from `build_target`.
83    pub fn spec(&self) -> ContainedSpec {
84        let mut spec = ContainedSpec::new(&self.name, &self.host_column, self.kind);
85        if let Some(id) = &self.id_column {
86            spec = spec.with_id_column(id);
87        }
88        spec
89    }
90}