Skip to main content

toasty_core/stmt/
query.rs

1use super::{
2    Delete, Expr, ExprSet, Limit, Node, OrderBy, Path, Returning, Select, Source, Statement,
3    Update, UpdateTarget, Values, Visit, VisitMut, With,
4};
5use crate::stmt::{self, Filter};
6
7/// A query statement that reads data from the database.
8///
9/// `Query` wraps a set expression body (typically a [`Select`]) with optional
10/// ordering, limits, CTEs, and row-level locks. It is the read-side counterpart
11/// to [`Insert`], [`Update`], and [`Delete`].
12///
13/// # Examples
14///
15/// ```ignore
16/// use toasty_core::stmt::{Query, Values, ExprSet};
17///
18/// // A unit query that returns one empty row
19/// let q = Query::unit();
20/// assert!(matches!(q.body, ExprSet::Values(_)));
21/// assert!(!q.single);
22/// ```
23#[derive(Debug, Clone, PartialEq)]
24pub struct Query {
25    /// Optional common table expressions (CTEs) for this query.
26    pub with: Option<With>,
27
28    /// The body of the query. Either `SELECT`, `UNION`, `VALUES`, or possibly
29    /// other types of queries depending on database support.
30    pub body: ExprSet,
31
32    /// When `true`, the query returns a single record instead of a list.
33    ///
34    /// This is semantically different from `LIMIT 1`: it indicates there can
35    /// only ever be one matching result. The return type becomes `Record`
36    /// instead of `List`.
37    pub single: bool,
38
39    /// Optional `ORDER BY` clause.
40    pub order_by: Option<OrderBy>,
41
42    /// Optional `LIMIT` and `OFFSET` clause.
43    pub limit: Option<Limit>,
44
45    /// Row-level locks (`FOR UPDATE`, `FOR SHARE`).
46    pub locks: Vec<Lock>,
47}
48
49/// A row-level lock to acquire when executing a query.
50///
51/// Corresponds to SQL's `FOR UPDATE` and `FOR SHARE` clauses. Only meaningful
52/// for SQL databases that support row-level locking.
53///
54/// # Examples
55///
56/// ```ignore
57/// use toasty_core::stmt::Lock;
58///
59/// let lock = Lock::Update;
60/// ```
61#[derive(Debug, Clone, PartialEq)]
62pub enum Lock {
63    /// `FOR UPDATE` -- acquire an exclusive lock on matched rows.
64    Update,
65    /// `FOR SHARE` -- acquire a shared lock on matched rows.
66    Share,
67}
68
69/// Builder for constructing [`Query`] instances with optional clauses.
70///
71/// Created via [`Query::builder`]. Allows chaining calls to add CTEs, filters,
72/// returning clauses, and locks before calling [`build`](QueryBuilder::build).
73///
74/// # Examples
75///
76/// ```ignore
77/// use toasty_core::stmt::{Query, Select, Source, Filter};
78///
79/// let select = Select::new(Source::from(ModelId(0)), Filter::ALL);
80/// let query = Query::builder(select).build();
81/// ```
82#[derive(Debug)]
83pub struct QueryBuilder {
84    query: Query,
85}
86
87impl Query {
88    /// Creates a new query with the given body and default options (no ordering,
89    /// no limit, not single, no locks).
90    pub fn new(body: impl Into<ExprSet>) -> Self {
91        Self {
92            with: None,
93            body: body.into(),
94            single: false,
95            order_by: None,
96            limit: None,
97            locks: vec![],
98        }
99    }
100
101    /// Creates a new query that returns exactly one record (`single = true`).
102    pub fn new_single(body: impl Into<ExprSet>) -> Self {
103        Self {
104            with: None,
105            body: body.into(),
106            single: true,
107            order_by: None,
108            limit: None,
109            locks: vec![],
110        }
111    }
112
113    /// Creates a new `SELECT` query from a source and filter.
114    pub fn new_select(source: impl Into<Source>, filter: impl Into<Filter>) -> Self {
115        Self::builder(Select::new(source, filter)).build()
116    }
117
118    /// Returns a [`QueryBuilder`] initialized with the given body.
119    pub fn builder(body: impl Into<ExprSet>) -> QueryBuilder {
120        QueryBuilder {
121            query: Query::new(body),
122        }
123    }
124
125    /// Creates a unit query that produces one empty row (empty `VALUES`).
126    pub fn unit() -> Self {
127        Query::new(Values::default())
128    }
129
130    /// Creates a query whose body is a `VALUES` expression.
131    pub fn values(values: impl Into<Values>) -> Self {
132        Self {
133            with: None,
134            body: ExprSet::Values(values.into()),
135            single: false,
136            order_by: None,
137            limit: None,
138            locks: vec![],
139        }
140    }
141
142    /// Converts this query into an [`Update`] statement targeting the same
143    /// source. The query must have a `SELECT` body with a model source.
144    pub fn update(self) -> Update {
145        let ExprSet::Select(select) = &self.body else {
146            todo!("stmt={self:#?}");
147        };
148
149        assert!(select.source.is_model());
150
151        stmt::Update {
152            target: UpdateTarget::Query(Box::new(self)),
153            assignments: stmt::Assignments::default(),
154            filter: Filter::default(),
155            condition: stmt::Condition::default(),
156            returning: None,
157        }
158    }
159
160    /// Converts this query into a [`Delete`] statement. The query body must
161    /// be a `SELECT`.
162    pub fn delete(self) -> Delete {
163        match self.body {
164            ExprSet::Select(select) => Delete {
165                from: select.source,
166                filter: select.filter,
167                returning: None,
168                condition: Default::default(),
169            },
170            _ => todo!("{self:#?}"),
171        }
172    }
173
174    /// Adds a filter to this query's `SELECT` body.
175    ///
176    /// # Panics
177    ///
178    /// Panics if the query body is not a `SELECT`.
179    pub fn add_filter(&mut self, filter: impl Into<Filter>) {
180        self.body.as_select_mut_unwrap().add_filter(filter);
181    }
182
183    /// Sets the filter for this query's `SELECT` body overwriting any existing one.
184    ///
185    /// # Panics
186    ///
187    /// Panics if the query body is not a `SELECT`.
188    pub fn set_filter(&mut self, filter: impl Into<Filter>) {
189        self.body.as_select_mut_unwrap().filter = filter.into();
190    }
191
192    /// Adds an association include path to this query's `SELECT` body.
193    pub fn include(&mut self, path: impl Into<Path>) {
194        match &mut self.body {
195            ExprSet::Select(body) => body.include(path),
196            _ => todo!(),
197        }
198    }
199}
200
201impl Statement {
202    /// Returns `true` if this statement is a [`Query`].
203    pub fn is_query(&self) -> bool {
204        matches!(self, Statement::Query(_))
205    }
206
207    /// Attempts to return a reference to an inner [`Query`].
208    ///
209    /// * If `self` is a [`Statement::Query`], a reference to the inner [`Query`] is
210    ///   returned wrapped in [`Some`].
211    /// * Else, [`None`] is returned.
212    pub fn as_query(&self) -> Option<&Query> {
213        match self {
214            Self::Query(query) => Some(query),
215            _ => None,
216        }
217    }
218
219    /// Returns a mutable reference to the inner [`Query`], if this is a query statement.
220    ///
221    /// * If `self` is a [`Statement::Query`], a mutable reference to the inner [`Query`] is
222    ///   returned wrapped in [`Some`].
223    /// * Else, [`None`] is returned.
224    pub fn as_query_mut(&mut self) -> Option<&mut Query> {
225        match self {
226            Self::Query(query) => Some(query),
227            _ => None,
228        }
229    }
230
231    /// Consumes `self` and attempts to return the inner [`Query`].
232    ///
233    /// Returns `None` if `self` is not a [`Statement::Query`].
234    pub fn into_query(self) -> Option<Query> {
235        match self {
236            Self::Query(query) => Some(query),
237            _ => None,
238        }
239    }
240
241    /// Consumes `self` and returns the inner [`Query`].
242    ///
243    /// # Panics
244    ///
245    /// If `self` is not a [`Statement::Query`].
246    #[track_caller]
247    pub fn into_query_unwrap(self) -> Query {
248        match self {
249            Self::Query(query) => query,
250            v => panic!("expected `Query`, found {v:#?}"),
251        }
252    }
253}
254
255impl From<Query> for Statement {
256    fn from(value: Query) -> Self {
257        Self::Query(value)
258    }
259}
260
261impl Node for Query {
262    fn visit<V: Visit>(&self, mut visit: V) {
263        visit.visit_stmt_query(self);
264    }
265
266    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
267        visit.visit_stmt_query_mut(self);
268    }
269}
270
271impl QueryBuilder {
272    /// Sets the `WITH` (CTE) clause for the query being built.
273    pub fn with(mut self, with: impl Into<With>) -> Self {
274        self.query.with = Some(with.into());
275        self
276    }
277
278    /// Sets the row-level locks for the query being built.
279    pub fn locks(mut self, locks: impl Into<Vec<Lock>>) -> Self {
280        self.query.locks = locks.into();
281        self
282    }
283
284    /// Sets the filter on the query's `SELECT` body.
285    ///
286    /// # Panics
287    ///
288    /// Panics if the query body is not a `SELECT`.
289    pub fn filter(mut self, filter: impl Into<Filter>) -> Self {
290        let filter = filter.into();
291
292        match &mut self.query.body {
293            ExprSet::Select(select) => {
294                select.filter = filter;
295            }
296            _ => todo!("query={self:#?}"),
297        }
298
299        self
300    }
301
302    /// Sets the returning clause on the query's `SELECT` body.
303    pub fn returning(mut self, returning: Returning) -> Self {
304        match &mut self.query.body {
305            ExprSet::Select(select) => {
306                select.returning = returning;
307            }
308            _ => todo!(),
309        }
310
311        self
312    }
313
314    /// Sets the returning clause to `Returning::Project` containing the given
315    /// expression.
316    pub fn returning_project(self, expr: impl Into<Expr>) -> Self {
317        self.returning(Returning::Project(expr.into()))
318    }
319
320    /// Sets the returning clause to `Returning::Expr` containing the given
321    /// expression.
322    pub fn returning_expr(self, expr: impl Into<Expr>) -> Self {
323        self.returning(Returning::Expr(expr.into()))
324    }
325
326    /// Consumes this builder and returns the constructed [`Query`].
327    pub fn build(self) -> Query {
328        self.query
329    }
330}
331
332impl From<QueryBuilder> for Query {
333    fn from(value: QueryBuilder) -> Self {
334        value.build()
335    }
336}