toasty_core/stmt/
expr_reference.rs

1use crate::{
2    schema::{app::FieldId, db::ColumnId},
3    stmt::Expr,
4};
5
6/// A reference to a model, field, or column.
7///
8/// References use scope-based nesting to support subqueries. A nesting level of
9/// `0` refers to the current query scope, while higher levels reference higher
10/// scope queries.
11///
12/// # Examples
13///
14/// ```text
15/// ref(field: 0, nesting: 0)  // field 0 in current query
16/// ref(field: 2, nesting: 1)  // field 2 in parent query
17/// ref(column: 0, table: 1)   // column 0 in table 1
18/// ```
19#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
20pub enum ExprReference {
21    /// A reference to a column in a database-level statement.
22    ///
23    /// ExprReference::Column represents resolved column references after lowering from the
24    /// application schema to the database schema. It uses a scope-based approach
25    /// similar to ExprReference::Field, referencing a specific column within a target
26    /// at a given nesting level.
27    Column(ExprColumn),
28
29    /// Reference a specific field in a query's relation.
30    ///
31    /// For Query/Delete statements, the relation is the Source.
32    /// For Insert/Update statements, the relation is the target.
33    Field {
34        /// Query scope nesting level: 0 = current query, 1+ = higher scope queries
35        nesting: usize,
36        /// Index of the field within the relation
37        index: usize,
38    },
39
40    /// Reference a model at a specific nesting level.
41    ///
42    /// This is roughly referencing the full record instead of a specific field.
43    Model { nesting: usize },
44}
45
46/// A reference to a database column.
47///
48/// Used after lowering from the application schema to the database schema.
49#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
50pub struct ExprColumn {
51    /// Query scope nesting level: `0` = current query, `1`+ = higher scope queries.
52    pub nesting: usize,
53
54    /// Index into the table references vector for this column's source relation.
55    ///
56    /// For statements with multiple tables (SELECT with JOINs), this indexes into
57    /// the `SourceTable::tables` field to identify which specific table contains
58    /// this column. For single-target statements (INSERT, UPDATE), this is
59    /// typically 0 since these operations target only one relation at a time.
60    pub table: usize,
61
62    /// The index of the column in the table
63    pub column: usize,
64}
65
66impl Expr {
67    pub fn is_expr_reference(&self) -> bool {
68        matches!(self, Expr::Reference(..))
69    }
70
71    /// Creates an expression that references a field in the current query.
72    ///
73    /// This creates an `ExprReference::Field` with `nesting = 0`, meaning it
74    /// references a field in the current query scope rather than an outer query.
75    ///
76    /// # Arguments
77    ///
78    /// * `field` - A field identifier that can be converted into a `FieldId`
79    ///
80    /// # Returns
81    ///
82    /// An `Expr::Reference` containing an `ExprReference::Field` that points to
83    /// the specified field in the current query's relation.
84    pub fn ref_self_field(field: impl Into<FieldId>) -> Self {
85        ExprReference::field(field).into()
86    }
87
88    /// Create a reference to a field at a specified nesting level
89    pub fn ref_field(nesting: usize, field: impl Into<FieldId>) -> Self {
90        ExprReference::Field {
91            nesting,
92            index: field.into().index,
93        }
94        .into()
95    }
96
97    /// Create a reference to a field one level up
98    pub fn ref_parent_field(field: impl Into<FieldId>) -> Self {
99        ExprReference::Field {
100            nesting: 1,
101            index: field.into().index,
102        }
103        .into()
104    }
105
106    pub fn is_field(&self) -> bool {
107        matches!(self, Self::Reference(ExprReference::Field { .. }))
108    }
109
110    /// Create a model reference to the parent model
111    pub fn ref_parent_model() -> Self {
112        Self::ref_ancestor_model(1)
113    }
114
115    /// Create a model reference to the specified nesting level
116    pub fn ref_ancestor_model(nesting: usize) -> Self {
117        ExprReference::Model { nesting }.into()
118    }
119
120    pub fn column(column: impl Into<ExprReference>) -> Self {
121        column.into().into()
122    }
123
124    pub fn is_column(&self) -> bool {
125        matches!(self, Self::Reference(ExprReference::Column(..)))
126    }
127
128    pub fn as_expr_reference(&self) -> Option<&ExprReference> {
129        match self {
130            Expr::Reference(expr_reference) => Some(expr_reference),
131            _ => None,
132        }
133    }
134
135    #[track_caller]
136    pub fn as_expr_reference_unwrap(&self) -> &ExprReference {
137        self.as_expr_reference()
138            .unwrap_or_else(|| panic!("expected ExprReference; actual={self:#?}"))
139    }
140
141    pub fn as_expr_column(&self) -> Option<&ExprColumn> {
142        match self {
143            Expr::Reference(ExprReference::Column(expr_column)) => Some(expr_column),
144            _ => None,
145        }
146    }
147
148    #[track_caller]
149    pub fn as_expr_column_unwrap(&self) -> &ExprColumn {
150        self.as_expr_column()
151            .unwrap_or_else(|| panic!("expected ExprColumn; actual={self:#?}"))
152    }
153}
154
155impl ExprReference {
156    pub fn field(field: impl Into<FieldId>) -> Self {
157        ExprReference::Field {
158            nesting: 0,
159            index: field.into().index,
160        }
161    }
162
163    pub fn is_field(&self) -> bool {
164        matches!(self, ExprReference::Field { .. })
165    }
166
167    pub fn is_model(&self) -> bool {
168        matches!(self, ExprReference::Model { .. })
169    }
170
171    pub fn column(table: usize, column: usize) -> Self {
172        ExprReference::Column(ExprColumn {
173            nesting: 0,
174            table,
175            column,
176        })
177    }
178
179    pub fn is_column(&self) -> bool {
180        matches!(self, ExprReference::Column(..))
181    }
182
183    pub fn as_expr_column(&self) -> Option<&ExprColumn> {
184        match self {
185            ExprReference::Column(expr_column) => Some(expr_column),
186            _ => None,
187        }
188    }
189
190    #[track_caller]
191    pub fn as_expr_column_unwrap(&self) -> &ExprColumn {
192        match self {
193            ExprReference::Column(expr_column) => expr_column,
194            _ => panic!("expected ExprColumn; actual={self:#?}"),
195        }
196    }
197
198    #[track_caller]
199    pub fn as_expr_column_mut_unwrap(&mut self) -> &mut ExprColumn {
200        match self {
201            ExprReference::Column(expr_column) => expr_column,
202            _ => panic!("expected ExprColumn; actual={self:#?}"),
203        }
204    }
205}
206
207impl From<ExprColumn> for ExprReference {
208    fn from(value: ExprColumn) -> Self {
209        ExprReference::Column(value)
210    }
211}
212
213impl From<ExprReference> for Expr {
214    fn from(value: ExprReference) -> Self {
215        Expr::Reference(value)
216    }
217}
218
219impl From<ExprColumn> for Expr {
220    fn from(value: ExprColumn) -> Self {
221        Expr::Reference(ExprReference::Column(value))
222    }
223}
224
225impl From<&ExprReference> for Expr {
226    fn from(value: &ExprReference) -> Self {
227        Expr::Reference(*value)
228    }
229}
230
231impl From<&ExprColumn> for Expr {
232    fn from(value: &ExprColumn) -> Self {
233        Expr::Reference(ExprReference::Column(*value))
234    }
235}
236
237impl From<ColumnId> for ExprReference {
238    fn from(value: ColumnId) -> Self {
239        ExprReference::Column(value.into())
240    }
241}
242
243impl From<ColumnId> for ExprColumn {
244    fn from(value: ColumnId) -> Self {
245        ExprColumn {
246            nesting: 0,
247            table: 0,
248            column: value.index,
249        }
250    }
251}