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}