Skip to main content

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    /// When `true`, the query removes duplicate rows (`SELECT DISTINCT`).
37    /// Used by multi-step (`via`) `.include()` lowering, where a `JOIN`
38    /// through the path can yield duplicate target rows that must collapse
39    /// to distinct targets.
40    pub distinct: bool,
41}
42
43impl Select {
44    /// Creates a new `Select` with the given source and filter, defaulting to
45    /// a model-level returning clause with no includes.
46    pub fn new(source: impl Into<Source>, filter: impl Into<Filter>) -> Self {
47        Self {
48            returning: Returning::Model { include: vec![] },
49            source: source.into(),
50            filter: filter.into(),
51            distinct: false,
52        }
53    }
54
55    /// Adds an association include path to the returning clause.
56    ///
57    /// # Panics
58    ///
59    /// Panics if the returning clause is not `Returning::Model`.
60    pub(crate) fn include(&mut self, path: impl Into<Path>) {
61        match &mut self.returning {
62            Returning::Model { include } => include.push(path.into()),
63            _ => panic!("Expected Returning::Model for include operation"),
64        }
65    }
66
67    /// Adds an additional filter, AND-ing it with any existing filter.
68    pub fn add_filter(&mut self, filter: impl Into<Filter>) {
69        self.filter.add_filter(filter);
70    }
71}
72
73impl Statement {
74    /// If this is a query with a `SELECT` body, returns a reference to that
75    /// [`Select`]. Returns `None` otherwise.
76    pub fn query_select(&self) -> Option<&Select> {
77        self.as_query().and_then(|query| query.body.as_select())
78    }
79
80    /// Returns a reference to this statement's inner [`Select`].
81    ///
82    /// # Panics
83    ///
84    /// Panics if this is not a query statement with a `SELECT` body.
85    #[track_caller]
86    pub fn query_select_unwrap(&self) -> &Select {
87        match self {
88            Statement::Query(query) => match &query.body {
89                ExprSet::Select(select) => select,
90                _ => panic!("expected `Select`; actual={self:#?}"),
91            },
92            _ => panic!("expected `Select`; actual={self:#?}"),
93        }
94    }
95}
96
97impl Query {
98    /// Consumes this query and returns the inner [`Select`].
99    ///
100    /// # Panics
101    ///
102    /// Panics if the query body is not a `SELECT`.
103    pub fn into_select(self) -> Select {
104        self.body.into_select()
105    }
106}
107
108impl ExprSet {
109    /// Returns a reference to the inner [`Select`] if this is a `Select` variant.
110    pub fn as_select(&self) -> Option<&Select> {
111        match self {
112            Self::Select(expr) => Some(expr),
113            _ => None,
114        }
115    }
116
117    /// Returns a reference to the inner [`Select`], panicking if this is not a
118    /// `Select` variant.
119    #[track_caller]
120    pub fn as_select_unwrap(&self) -> &Select {
121        self.as_select()
122            .unwrap_or_else(|| panic!("expected `Select`; actual={self:#?}"))
123    }
124
125    /// Returns a mutable reference to the inner [`Select`] if this is a
126    /// `Select` variant.
127    pub fn as_select_mut(&mut self) -> Option<&mut Select> {
128        match self {
129            Self::Select(expr) => Some(expr),
130            _ => None,
131        }
132    }
133
134    /// Returns a mutable reference to the inner [`Select`], panicking if this
135    /// is not a `Select` variant.
136    #[track_caller]
137    pub fn as_select_mut_unwrap(&mut self) -> &mut Select {
138        match self {
139            Self::Select(select) => select,
140            _ => panic!("expected `Select`; actual={self:#?}"),
141        }
142    }
143
144    /// Consumes this `ExprSet` and returns the inner [`Select`].
145    ///
146    /// # Panics
147    ///
148    /// Panics if this is not a `Select` variant.
149    #[track_caller]
150    pub fn into_select(self) -> Select {
151        match self {
152            Self::Select(expr) => *expr,
153            _ => todo!(),
154        }
155    }
156
157    /// Returns `true` if this is a `Select` variant.
158    pub fn is_select(&self) -> bool {
159        matches!(self, Self::Select(_))
160    }
161}
162
163impl From<Select> for Statement {
164    fn from(value: Select) -> Self {
165        Self::Query(value.into())
166    }
167}
168
169impl From<Select> for Query {
170    fn from(value: Select) -> Self {
171        Self::builder(value).build()
172    }
173}
174
175impl From<TableId> for Select {
176    fn from(value: TableId) -> Self {
177        Self::new(Source::table(value), true)
178    }
179}
180
181impl From<SourceModel> for Select {
182    fn from(value: SourceModel) -> Self {
183        Self::new(Source::Model(value), true)
184    }
185}
186
187impl Node for Select {
188    fn visit<V: Visit>(&self, mut visit: V) {
189        visit.visit_stmt_select(self);
190    }
191
192    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
193        visit.visit_stmt_select_mut(self);
194    }
195}