toasty_core/stmt/
select.rs

1use super::{Node, Path, Query, Returning, Source, SourceModel, Statement, Visit, VisitMut};
2use crate::{
3    schema::db::TableId,
4    stmt::{ExprSet, Filter},
5};
6
7/// A `SELECT` expression within a query body.
8///
9/// Represents the combination of a data source, a filter (WHERE clause), and a
10/// projection (RETURNING/SELECT list). This is the most common query body type.
11///
12/// At the model level, the source is a model with optional association includes.
13/// After lowering, the source becomes a table with joins.
14///
15/// # Examples
16///
17/// ```ignore
18/// use toasty_core::stmt::{Select, Source, Filter};
19/// use toasty_core::schema::app::ModelId;
20///
21/// let select = Select::new(Source::from(ModelId(0)), Filter::ALL);
22/// assert!(select.source.is_model());
23/// ```
24#[derive(Debug, Clone, PartialEq)]
25pub struct Select {
26    /// The projection (what columns/fields to return).
27    pub returning: Returning,
28
29    /// The data source (`FROM` clause). At the model level this is a model
30    /// reference; at the table level this is a table with joins.
31    pub source: Source,
32
33    /// The filter (`WHERE` clause).
34    pub filter: Filter,
35}
36
37impl Select {
38    /// Creates a new `Select` with the given source and filter, defaulting to
39    /// a model-level returning clause with no includes.
40    pub fn new(source: impl Into<Source>, filter: impl Into<Filter>) -> Self {
41        Self {
42            returning: Returning::Model { include: vec![] },
43            source: source.into(),
44            filter: filter.into(),
45        }
46    }
47
48    /// Adds an association include path to the returning clause.
49    ///
50    /// # Panics
51    ///
52    /// Panics if the returning clause is not `Returning::Model`.
53    pub(crate) fn include(&mut self, path: impl Into<Path>) {
54        match &mut self.returning {
55            Returning::Model { include } => include.push(path.into()),
56            _ => panic!("Expected Returning::Model for include operation"),
57        }
58    }
59
60    /// Adds an additional filter, AND-ing it with any existing filter.
61    pub fn add_filter(&mut self, filter: impl Into<Filter>) {
62        self.filter.add_filter(filter);
63    }
64}
65
66impl Statement {
67    /// If this is a query with a `SELECT` body, returns a reference to that
68    /// [`Select`]. Returns `None` otherwise.
69    pub fn query_select(&self) -> Option<&Select> {
70        self.as_query().and_then(|query| query.body.as_select())
71    }
72
73    /// Returns a reference to this statement's inner [`Select`].
74    ///
75    /// # Panics
76    ///
77    /// Panics if this is not a query statement with a `SELECT` body.
78    #[track_caller]
79    pub fn query_select_unwrap(&self) -> &Select {
80        match self {
81            Statement::Query(query) => match &query.body {
82                ExprSet::Select(select) => select,
83                _ => panic!("expected `Select`; actual={self:#?}"),
84            },
85            _ => panic!("expected `Select`; actual={self:#?}"),
86        }
87    }
88}
89
90impl Query {
91    /// Consumes this query and returns the inner [`Select`].
92    ///
93    /// # Panics
94    ///
95    /// Panics if the query body is not a `SELECT`.
96    pub fn into_select(self) -> Select {
97        self.body.into_select()
98    }
99}
100
101impl ExprSet {
102    /// Returns a reference to the inner [`Select`] if this is a `Select` variant.
103    pub fn as_select(&self) -> Option<&Select> {
104        match self {
105            Self::Select(expr) => Some(expr),
106            _ => None,
107        }
108    }
109
110    /// Returns a reference to the inner [`Select`], panicking if this is not a
111    /// `Select` variant.
112    #[track_caller]
113    pub fn as_select_unwrap(&self) -> &Select {
114        self.as_select()
115            .unwrap_or_else(|| panic!("expected `Select`; actual={self:#?}"))
116    }
117
118    /// Returns a mutable reference to the inner [`Select`] if this is a
119    /// `Select` variant.
120    pub fn as_select_mut(&mut self) -> Option<&mut Select> {
121        match self {
122            Self::Select(expr) => Some(expr),
123            _ => None,
124        }
125    }
126
127    /// Returns a mutable reference to the inner [`Select`], panicking if this
128    /// is not a `Select` variant.
129    #[track_caller]
130    pub fn as_select_mut_unwrap(&mut self) -> &mut Select {
131        match self {
132            Self::Select(select) => select,
133            _ => panic!("expected `Select`; actual={self:#?}"),
134        }
135    }
136
137    /// Consumes this `ExprSet` and returns the inner [`Select`].
138    ///
139    /// # Panics
140    ///
141    /// Panics if this is not a `Select` variant.
142    #[track_caller]
143    pub fn into_select(self) -> Select {
144        match self {
145            Self::Select(expr) => *expr,
146            _ => todo!(),
147        }
148    }
149
150    /// Returns `true` if this is a `Select` variant.
151    pub fn is_select(&self) -> bool {
152        matches!(self, Self::Select(_))
153    }
154}
155
156impl From<Select> for Statement {
157    fn from(value: Select) -> Self {
158        Self::Query(value.into())
159    }
160}
161
162impl From<Select> for Query {
163    fn from(value: Select) -> Self {
164        Self::builder(value).build()
165    }
166}
167
168impl From<TableId> for Select {
169    fn from(value: TableId) -> Self {
170        Self::new(Source::table(value), true)
171    }
172}
173
174impl From<SourceModel> for Select {
175    fn from(value: SourceModel) -> Self {
176        Self::new(Source::Model(value), true)
177    }
178}
179
180impl Node for Select {
181    fn visit<V: Visit>(&self, mut visit: V) {
182        visit.visit_stmt_select(self);
183    }
184
185    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
186        visit.visit_stmt_select_mut(self);
187    }
188}