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}