toasty_core/stmt/
source.rs

1use super::{Association, SourceTable, SourceTableId, TableFactor, TableRef, TableWithJoins};
2use crate::{
3    schema::{
4        app::{ModelId, ModelRoot},
5        db::TableId,
6    },
7    stmt::ExprArg,
8};
9
10/// The data source for a [`Select`](super::Select) statement's `FROM` clause.
11///
12/// At the model level, the source references a model (optionally navigated via
13/// an association). After lowering, the source becomes a table with joins.
14///
15/// # Examples
16///
17/// ```ignore
18/// use toasty_core::stmt::Source;
19/// use toasty_core::schema::app::ModelId;
20///
21/// let source = Source::from(ModelId(0));
22/// assert!(source.is_model());
23/// assert!(!source.is_table());
24/// ```
25#[derive(Debug, Clone, PartialEq)]
26pub enum Source {
27    /// Source is a model (app-level).
28    Model(SourceModel),
29
30    /// Source is a database table (lowered/DB-level).
31    Table(SourceTable),
32}
33
34/// A model-level data source.
35///
36/// References a model by ID and optionally specifies an association traversal
37/// path used to reach this model from another query.
38///
39/// # Examples
40///
41/// ```ignore
42/// use toasty_core::stmt::SourceModel;
43/// use toasty_core::schema::app::ModelId;
44///
45/// let source = SourceModel { id: ModelId(0), via: None };
46/// ```
47#[derive(Debug, Clone, PartialEq)]
48pub struct SourceModel {
49    /// The model being selected.
50    pub id: ModelId,
51
52    /// If set, the model is reached via this association from another query.
53    pub via: Option<Association>,
54}
55
56impl Source {
57    /// Creates a table source from explicit table refs and a table-with-joins
58    /// specification.
59    pub fn table_with_joins(tables: Vec<TableRef>, from_item: TableWithJoins) -> Self {
60        let source_table = SourceTable::new(tables, from_item);
61        Self::Table(source_table)
62    }
63
64    /// Returns `true` if this is a `Model` source.
65    pub fn is_model(&self) -> bool {
66        matches!(self, Self::Model(_))
67    }
68
69    /// Returns a reference to the inner [`SourceModel`] if this is a `Model`
70    /// variant.
71    pub fn as_model(&self) -> Option<&SourceModel> {
72        match self {
73            Self::Model(source) => Some(source),
74            _ => None,
75        }
76    }
77
78    /// Returns a reference to the inner [`SourceModel`].
79    ///
80    /// # Panics
81    ///
82    /// Panics if this is not a `Model` source.
83    #[track_caller]
84    pub fn as_model_unwrap(&self) -> &SourceModel {
85        self.as_model()
86            .expect("expected SourceModel; actual={self:#?}")
87    }
88
89    /// Returns the model ID if this is a `Model` source.
90    pub fn model_id(&self) -> Option<ModelId> {
91        self.as_model().map(|source_model| source_model.id)
92    }
93
94    /// Returns the model ID, panicking if this is not a `Model` source.
95    pub fn model_id_unwrap(&self) -> ModelId {
96        self.as_model_unwrap().id
97    }
98
99    /// Returns `true` if this is a `Table` source.
100    pub fn is_table(&self) -> bool {
101        matches!(self, Self::Table(_))
102    }
103
104    /// Creates a `Table` source from a single table reference with no joins.
105    pub fn table(table: impl Into<TableRef>) -> Self {
106        let table_ref = table.into();
107        let source_table = SourceTable::new(
108            vec![table_ref],
109            TableWithJoins {
110                relation: TableFactor::Table(SourceTableId(0)),
111                joins: vec![],
112            },
113        );
114        Self::Table(source_table)
115    }
116
117    /// Returns a reference to the inner [`SourceTable`] if this is a `Table`
118    /// variant.
119    pub fn as_table(&self) -> Option<&SourceTable> {
120        match self {
121            Self::Table(source) => Some(source),
122            _ => None,
123        }
124    }
125
126    /// Returns a reference to the inner [`SourceTable`].
127    ///
128    /// # Panics
129    ///
130    /// Panics if this is not a `Table` source.
131    #[track_caller]
132    pub fn as_table_unwrap(&self) -> &SourceTable {
133        self.as_table()
134            .unwrap_or_else(|| panic!("expected SourceTable; actual={self:#?}"))
135    }
136}
137
138impl From<&ModelRoot> for Source {
139    fn from(value: &ModelRoot) -> Self {
140        Self::from(value.id)
141    }
142}
143
144impl From<ModelId> for Source {
145    fn from(value: ModelId) -> Self {
146        Self::Model(SourceModel {
147            id: value,
148            via: None,
149        })
150    }
151}
152
153impl From<TableId> for Source {
154    fn from(value: TableId) -> Self {
155        Self::table(value)
156    }
157}
158
159impl From<TableRef> for Source {
160    fn from(value: TableRef) -> Self {
161        Self::table(value)
162    }
163}
164
165impl From<ExprArg> for Source {
166    fn from(value: ExprArg) -> Self {
167        Source::Table(SourceTable::from(value))
168    }
169}