Skip to main content

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 {
44        /// Query scope nesting level: 0 = current query, 1+ = higher scope queries.
45        nesting: usize,
46    },
47}
48
49/// A reference to a database column.
50///
51/// Used after lowering from the application schema to the database schema.
52#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
53pub struct ExprColumn {
54    /// Query scope nesting level: `0` = current query, `1`+ = higher scope queries.
55    pub nesting: usize,
56
57    /// Index into the table references vector for this column's source relation.
58    ///
59    /// For statements with multiple tables (SELECT with JOINs), this indexes into
60    /// the `SourceTable::tables` field to identify which specific table contains
61    /// this column. For single-target statements (INSERT, UPDATE), this is
62    /// typically 0 since these operations target only one relation at a time.
63    pub table: usize,
64
65    /// The index of the column in the table
66    pub column: usize,
67}
68
69impl Expr {
70    /// Returns `true` if this expression is a reference (field, column, or model).
71    pub fn is_expr_reference(&self) -> bool {
72        matches!(self, Expr::Reference(..))
73    }
74
75    /// Creates an expression that references a field in the current query.
76    ///
77    /// This creates an `ExprReference::Field` with `nesting = 0`, meaning it
78    /// references a field in the current query scope rather than an outer query.
79    ///
80    /// # Arguments
81    ///
82    /// * `field` - A field identifier that can be converted into a `FieldId`
83    ///
84    /// # Returns
85    ///
86    /// An `Expr::Reference` containing an `ExprReference::Field` that points to
87    /// the specified field in the current query's relation.
88    pub fn ref_self_field(field: impl Into<FieldId>) -> Self {
89        ExprReference::field(field).into()
90    }
91
92    /// Create a reference to a field at a specified nesting level
93    pub fn ref_field(nesting: usize, field: impl Into<FieldId>) -> Self {
94        ExprReference::Field {
95            nesting,
96            index: field.into().index,
97        }
98        .into()
99    }
100
101    /// Create a reference to a field one level up
102    pub fn ref_parent_field(field: impl Into<FieldId>) -> Self {
103        ExprReference::Field {
104            nesting: 1,
105            index: field.into().index,
106        }
107        .into()
108    }
109
110    /// Returns `true` if this is a field reference.
111    pub fn is_field(&self) -> bool {
112        matches!(self, Self::Reference(ExprReference::Field { .. }))
113    }
114
115    /// Returns `true` if this references a field in the current query scope
116    /// (`nesting == 0`).
117    pub fn is_self_field(&self) -> bool {
118        self.as_self_field_index().is_some()
119    }
120
121    /// Returns the field index if this references a field in the current
122    /// query scope (`nesting == 0`), or `None` otherwise.
123    pub fn as_self_field_index(&self) -> Option<usize> {
124        match self {
125            Self::Reference(ExprReference::Field { nesting: 0, index }) => Some(*index),
126            _ => None,
127        }
128    }
129
130    /// Creates a model reference to the parent (nesting level 1).
131    pub fn ref_parent_model() -> Self {
132        Self::ref_ancestor_model(1)
133    }
134
135    /// Create a model reference to the specified nesting level
136    pub fn ref_ancestor_model(nesting: usize) -> Self {
137        ExprReference::Model { nesting }.into()
138    }
139
140    /// Creates a column reference expression.
141    pub fn column(column: impl Into<ExprReference>) -> Self {
142        column.into().into()
143    }
144
145    /// Returns `true` if this expression is a column reference.
146    pub fn is_column(&self) -> bool {
147        matches!(self, Self::Reference(ExprReference::Column(..)))
148    }
149
150    /// Returns a reference to the inner [`ExprReference`] if this is a
151    /// reference expression, or `None` otherwise.
152    pub fn as_expr_reference(&self) -> Option<&ExprReference> {
153        match self {
154            Expr::Reference(expr_reference) => Some(expr_reference),
155            _ => None,
156        }
157    }
158
159    /// Returns a reference to the inner [`ExprReference`].
160    ///
161    /// # Panics
162    ///
163    /// Panics if `self` is not `Expr::Reference`.
164    #[track_caller]
165    pub fn as_expr_reference_unwrap(&self) -> &ExprReference {
166        self.as_expr_reference()
167            .unwrap_or_else(|| panic!("expected ExprReference; actual={self:#?}"))
168    }
169
170    /// Returns a reference to the inner [`ExprColumn`] if this is a column
171    /// reference, or `None` otherwise.
172    pub fn as_expr_column(&self) -> Option<&ExprColumn> {
173        match self {
174            Expr::Reference(ExprReference::Column(expr_column)) => Some(expr_column),
175            _ => None,
176        }
177    }
178
179    /// Returns a reference to the inner [`ExprColumn`].
180    ///
181    /// # Panics
182    ///
183    /// Panics if `self` is not `Expr::Reference(ExprReference::Column(_))`.
184    #[track_caller]
185    pub fn as_expr_column_unwrap(&self) -> &ExprColumn {
186        self.as_expr_column()
187            .unwrap_or_else(|| panic!("expected ExprColumn; actual={self:#?}"))
188    }
189}
190
191impl ExprReference {
192    /// Creates a field reference in the current scope (nesting = 0).
193    pub fn field(field: impl Into<FieldId>) -> Self {
194        ExprReference::Field {
195            nesting: 0,
196            index: field.into().index,
197        }
198    }
199
200    /// Returns `true` if this is a field reference.
201    pub fn is_field(&self) -> bool {
202        matches!(self, ExprReference::Field { .. })
203    }
204
205    /// Returns `true` if this is a model reference.
206    pub fn is_model(&self) -> bool {
207        matches!(self, ExprReference::Model { .. })
208    }
209
210    /// Creates a column reference in the current scope (nesting = 0).
211    pub fn column(table: usize, column: usize) -> Self {
212        ExprReference::Column(ExprColumn {
213            nesting: 0,
214            table,
215            column,
216        })
217    }
218
219    /// Returns `true` if this is a column reference.
220    pub fn is_column(&self) -> bool {
221        matches!(self, ExprReference::Column(..))
222    }
223
224    /// Returns a reference to the inner [`ExprColumn`] if this is a column
225    /// reference, or `None` otherwise.
226    pub fn as_expr_column(&self) -> Option<&ExprColumn> {
227        match self {
228            ExprReference::Column(expr_column) => Some(expr_column),
229            _ => None,
230        }
231    }
232
233    /// Returns a reference to the inner [`ExprColumn`].
234    ///
235    /// # Panics
236    ///
237    /// Panics if `self` is not `ExprReference::Column`.
238    #[track_caller]
239    pub fn as_expr_column_unwrap(&self) -> &ExprColumn {
240        self.as_expr_column()
241            .unwrap_or_else(|| panic!("expected ExprColumn; actual={self:#?}"))
242    }
243
244    /// Returns a mutable reference to the inner [`ExprColumn`] if this is a
245    /// column reference, or `None` otherwise.
246    pub fn as_expr_column_mut(&mut self) -> Option<&mut ExprColumn> {
247        match self {
248            ExprReference::Column(expr_column) => Some(expr_column),
249            _ => None,
250        }
251    }
252
253    /// Returns a mutable reference to the inner [`ExprColumn`].
254    ///
255    /// # Panics
256    ///
257    /// Panics if `self` is not `ExprReference::Column`.
258    #[track_caller]
259    pub fn as_expr_column_mut_unwrap(&mut self) -> &mut ExprColumn {
260        match self {
261            ExprReference::Column(expr_column) => expr_column,
262            _ => panic!("expected ExprColumn; actual={self:#?}"),
263        }
264    }
265}
266
267impl From<ExprColumn> for ExprReference {
268    fn from(value: ExprColumn) -> Self {
269        ExprReference::Column(value)
270    }
271}
272
273impl From<ExprReference> for Expr {
274    fn from(value: ExprReference) -> Self {
275        Expr::Reference(value)
276    }
277}
278
279impl From<ExprColumn> for Expr {
280    fn from(value: ExprColumn) -> Self {
281        Expr::Reference(ExprReference::Column(value))
282    }
283}
284
285impl From<&ExprReference> for Expr {
286    fn from(value: &ExprReference) -> Self {
287        Expr::Reference(*value)
288    }
289}
290
291impl From<&ExprColumn> for Expr {
292    fn from(value: &ExprColumn) -> Self {
293        Expr::Reference(ExprReference::Column(*value))
294    }
295}
296
297impl From<ColumnId> for ExprReference {
298    fn from(value: ColumnId) -> Self {
299        ExprReference::Column(value.into())
300    }
301}
302
303impl From<ColumnId> for ExprColumn {
304    fn from(value: ColumnId) -> Self {
305        ExprColumn {
306            nesting: 0,
307            table: 0,
308            column: value.index,
309        }
310    }
311}