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