1use std::marker::PhantomData;
4
5use crate::capabilities::SupportsScalarSubqueryInSelect;
6use crate::error::QueryResult;
7use crate::filter::Filter;
8use crate::projection::ScalarProjection;
9use crate::relations::IncludeSpec;
10use crate::traits::{Model, ModelRelationLoader, QueryEngine};
11use crate::types::{OrderBy, Select};
12
13pub struct FindFirstOperation<E: QueryEngine, M: Model> {
29 engine: E,
30 filter: Filter,
31 order_by: OrderBy,
32 select: Select,
33 includes: Vec<IncludeSpec>,
37 pub extra_projections: Vec<ScalarProjection>,
40 _model: PhantomData<M>,
41}
42
43impl<E: QueryEngine, M: Model + crate::row::FromRow> FindFirstOperation<E, M> {
44 pub fn new(engine: E) -> Self {
46 Self {
47 engine,
48 filter: Filter::None,
49 order_by: OrderBy::none(),
50 select: Select::All,
51 includes: Vec::new(),
52 extra_projections: Vec::new(),
53 _model: PhantomData,
54 }
55 }
56
57 pub fn r#where(mut self, filter: impl Into<Filter>) -> Self {
59 let new_filter = filter.into();
60 self.filter = self.filter.and_then(new_filter);
61 self
62 }
63
64 pub fn order_by(mut self, order: impl Into<OrderBy>) -> Self {
66 self.order_by = order.into();
67 self
68 }
69
70 pub fn select(mut self, select: impl Into<Select>) -> Self {
72 self.select = select.into();
73 self
74 }
75
76 pub fn include(mut self, spec: IncludeSpec) -> Self {
78 self.includes.push(spec);
79 self
80 }
81
82 pub fn with_where_input<W: crate::inputs::WhereInput<Model = M>>(mut self, w: W) -> Self {
85 let f = w.into_ir();
86 self.filter = self.filter.and_then(f);
87 self
88 }
89
90 pub fn with_include_input<I: crate::inputs::IncludeInput<Model = M>>(mut self, i: I) -> Self {
93 let inc = i.into_ir();
94 for spec in inc.specs() {
95 self.includes.push(spec.clone());
96 }
97 self
98 }
99
100 pub fn with_select_input<S: crate::inputs::SelectInput<Model = M>>(mut self, s: S) -> Self {
102 self.select = s.into_ir();
103 self
104 }
105
106 pub fn with_order_by_input<O: crate::inputs::OrderByInput<Model = M>>(mut self, o: O) -> Self {
108 self.order_by = o.into_ir();
109 self
110 }
111
112 #[doc(hidden)]
115 pub fn filter_for_test(&self) -> &Filter {
116 &self.filter
117 }
118}
119
120impl<E, M> FindFirstOperation<E, M>
121where
122 E: QueryEngine + SupportsScalarSubqueryInSelect,
123 M: Model + crate::row::FromRow,
124{
125 pub fn with_scalar_projection(mut self, proj: ScalarProjection) -> Self {
133 self.extra_projections.push(proj);
134 self
135 }
136}
137
138impl<E: QueryEngine, M: Model + crate::row::FromRow> FindFirstOperation<E, M> {
139 pub fn build_sql(
141 &self,
142 dialect: &dyn crate::dialect::SqlDialect,
143 ) -> (String, Vec<crate::filter::FilterValue>) {
144 let proj_param_count: usize = self.extra_projections.iter().map(|p| p.params.len()).sum();
147 let (where_sql, where_params) = self.filter.to_sql(proj_param_count, dialect);
148
149 let mut params: Vec<crate::filter::FilterValue> =
150 Vec::with_capacity(proj_param_count + where_params.len());
151
152 let mut sql = String::new();
153
154 sql.push_str("SELECT ");
156 sql.push_str(&self.select.to_sql());
157
158 let mut proj_offset = 0usize;
160 for proj in &self.extra_projections {
161 sql.push_str(", ");
162 let frag = proj.to_sql(proj_offset, dialect, &mut params);
163 sql.push('(');
164 sql.push_str(&frag);
165 sql.push_str(") AS \"");
166 sql.push_str(proj.alias);
167 sql.push('"');
168 proj_offset += proj.params.len();
169 }
170
171 sql.push_str(" FROM ");
173 sql.push_str(M::TABLE_NAME);
174
175 if !self.filter.is_none() {
177 sql.push_str(" WHERE ");
178 sql.push_str(&where_sql);
179 }
180 params.extend(where_params);
181
182 if !self.order_by.is_empty() {
184 sql.push_str(" ORDER BY ");
185 sql.push_str(&self.order_by.to_sql());
186 }
187
188 sql.push_str(" LIMIT 1");
190
191 (sql, params)
192 }
193
194 pub async fn exec(self) -> QueryResult<Option<M>>
196 where
197 M: Send + 'static + ModelRelationLoader<E>,
198 {
199 let dialect = self.engine.dialect();
200 let (sql, params) = self.build_sql(dialect);
201 match self.engine.query_optional::<M>(&sql, params).await? {
202 None => Ok(None),
203 Some(row) => {
204 let mut parents = vec![row];
205 for spec in &self.includes {
206 <M as ModelRelationLoader<E>>::load_relation(&self.engine, &mut parents, spec)
207 .await?;
208 }
209 Ok(parents.into_iter().next())
210 }
211 }
212 }
213
214 pub async fn exec_required(self) -> QueryResult<M>
216 where
217 M: Send + 'static + ModelRelationLoader<E>,
218 {
219 let dialect = self.engine.dialect();
220 let (sql, params) = self.build_sql(dialect);
221 let row = self.engine.query_one::<M>(&sql, params).await?;
222 let mut parents = vec![row];
223 for spec in &self.includes {
224 <M as ModelRelationLoader<E>>::load_relation(&self.engine, &mut parents, spec).await?;
225 }
226 Ok(parents.into_iter().next().expect("1-element vec"))
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::error::QueryError;
234 use crate::filter::FilterValue;
235 use crate::types::OrderByField;
236
237 #[derive(Debug)]
238 struct TestModel;
239
240 impl Model for TestModel {
241 const MODEL_NAME: &'static str = "TestModel";
242 const TABLE_NAME: &'static str = "test_models";
243 const PRIMARY_KEY: &'static [&'static str] = &["id"];
244 const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
245 }
246
247 impl crate::row::FromRow for TestModel {
248 fn from_row(_row: &impl crate::row::RowRef) -> Result<Self, crate::row::RowError> {
249 Ok(TestModel)
250 }
251 }
252
253 impl crate::traits::ModelRelationLoader<MockEngine> for TestModel {
254 fn load_relation<'a>(
255 _engine: &'a MockEngine,
256 _parents: &'a mut [Self],
257 spec: &'a crate::relations::IncludeSpec,
258 ) -> crate::traits::BoxFuture<'a, QueryResult<()>> {
259 let name = spec.relation_name.clone();
260 Box::pin(async move {
261 Err(QueryError::internal(format!(
262 "unknown relation '{name}' on TestModel (mock)",
263 )))
264 })
265 }
266 }
267
268 #[derive(Clone)]
269 struct MockEngine;
270
271 impl QueryEngine for MockEngine {
272 fn dialect(&self) -> &dyn crate::dialect::SqlDialect {
273 &crate::dialect::Postgres
274 }
275
276 fn query_many<T: Model + crate::row::FromRow + Send + 'static>(
277 &self,
278 _sql: &str,
279 _params: Vec<FilterValue>,
280 ) -> crate::traits::BoxFuture<'_, QueryResult<Vec<T>>> {
281 Box::pin(async { Ok(Vec::new()) })
282 }
283
284 fn query_one<T: Model + crate::row::FromRow + Send + 'static>(
285 &self,
286 _sql: &str,
287 _params: Vec<FilterValue>,
288 ) -> crate::traits::BoxFuture<'_, QueryResult<T>> {
289 Box::pin(async { Err(QueryError::not_found("test")) })
290 }
291
292 fn query_optional<T: Model + crate::row::FromRow + Send + 'static>(
293 &self,
294 _sql: &str,
295 _params: Vec<FilterValue>,
296 ) -> crate::traits::BoxFuture<'_, QueryResult<Option<T>>> {
297 Box::pin(async { Ok(None) })
298 }
299
300 fn execute_insert<T: Model + crate::row::FromRow + Send + 'static>(
301 &self,
302 _sql: &str,
303 _params: Vec<FilterValue>,
304 ) -> crate::traits::BoxFuture<'_, QueryResult<T>> {
305 Box::pin(async { Err(QueryError::not_found("test")) })
306 }
307
308 fn execute_update<T: Model + crate::row::FromRow + Send + 'static>(
309 &self,
310 _sql: &str,
311 _params: Vec<FilterValue>,
312 ) -> crate::traits::BoxFuture<'_, QueryResult<Vec<T>>> {
313 Box::pin(async { Ok(Vec::new()) })
314 }
315
316 fn execute_delete(
317 &self,
318 _sql: &str,
319 _params: Vec<FilterValue>,
320 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
321 Box::pin(async { Ok(0) })
322 }
323
324 fn execute_raw(
325 &self,
326 _sql: &str,
327 _params: Vec<FilterValue>,
328 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
329 Box::pin(async { Ok(0) })
330 }
331
332 fn count(
333 &self,
334 _sql: &str,
335 _params: Vec<FilterValue>,
336 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
337 Box::pin(async { Ok(0) })
338 }
339 }
340
341 #[test]
344 fn test_find_first_new() {
345 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine);
346 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
347
348 assert!(sql.contains("SELECT * FROM test_models"));
349 assert!(sql.contains("LIMIT 1"));
350 assert!(params.is_empty());
351 }
352
353 #[test]
356 fn test_find_first_with_filter() {
357 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine).r#where(
358 Filter::Equals("status".into(), FilterValue::String("active".to_string())),
359 );
360
361 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
362
363 assert!(sql.contains("WHERE"));
364 assert!(sql.contains(r#""status" = $1"#));
365 assert_eq!(params.len(), 1);
366 }
367
368 #[test]
369 fn test_find_first_with_compound_filter() {
370 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
371 .r#where(Filter::Equals(
372 "department".into(),
373 FilterValue::String("engineering".to_string()),
374 ))
375 .r#where(Filter::Gt("salary".into(), FilterValue::Int(50000)));
376
377 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
378
379 assert!(sql.contains("WHERE"));
380 assert!(sql.contains("AND"));
381 assert_eq!(params.len(), 2);
382 }
383
384 #[test]
385 fn test_find_first_with_or_filter() {
386 let op =
387 FindFirstOperation::<MockEngine, TestModel>::new(MockEngine).r#where(Filter::or([
388 Filter::Equals("role".into(), FilterValue::String("admin".to_string())),
389 Filter::Equals("role".into(), FilterValue::String("superadmin".to_string())),
390 ]));
391
392 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
393
394 assert!(sql.contains("OR"));
395 assert_eq!(params.len(), 2);
396 }
397
398 #[test]
399 fn test_find_first_without_filter() {
400 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine);
401 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
402
403 assert!(!sql.contains("WHERE"));
404 assert!(params.is_empty());
405 }
406
407 #[test]
410 fn test_find_first_with_order() {
411 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
412 .r#where(Filter::Gt("age".into(), FilterValue::Int(18)))
413 .order_by(OrderByField::desc("created_at"));
414
415 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
416
417 assert!(sql.contains("WHERE"));
418 assert!(sql.contains("ORDER BY created_at DESC"));
419 assert!(sql.contains("LIMIT 1"));
420 assert_eq!(params.len(), 1);
421 }
422
423 #[test]
424 fn test_find_first_with_asc_order() {
425 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
426 .order_by(OrderByField::asc("name"));
427
428 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
429
430 assert!(sql.contains("ORDER BY name ASC"));
431 }
432
433 #[test]
434 fn test_find_first_without_order() {
435 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine);
436 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
437
438 assert!(!sql.contains("ORDER BY"));
439 }
440
441 #[test]
442 fn test_find_first_order_replaces() {
443 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
445 .order_by(OrderByField::asc("name"))
446 .order_by(OrderByField::desc("created_at"));
447
448 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
449
450 assert!(sql.contains("ORDER BY created_at DESC"));
451 assert!(!sql.contains("ORDER BY name"));
452 }
453
454 #[test]
457 fn test_find_first_with_select() {
458 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
459 .select(Select::fields(["id", "email"]));
460
461 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
462
463 assert!(sql.contains("SELECT id, email FROM"));
464 assert!(!sql.contains("SELECT *"));
465 }
466
467 #[test]
468 fn test_find_first_select_single_field() {
469 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
470 .select(Select::fields(["count"]));
471
472 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
473
474 assert!(sql.contains("SELECT count FROM"));
475 }
476
477 #[test]
480 fn test_find_first_sql_structure() {
481 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
482 .r#where(Filter::Equals("id".into(), FilterValue::Int(1)))
483 .order_by(OrderByField::desc("created_at"))
484 .select(Select::fields(["id", "name"]));
485
486 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
487
488 let select_pos = sql.find("SELECT").unwrap();
490 let from_pos = sql.find("FROM").unwrap();
491 let where_pos = sql.find("WHERE").unwrap();
492 let order_pos = sql.find("ORDER BY").unwrap();
493 let limit_pos = sql.find("LIMIT 1").unwrap();
494
495 assert!(select_pos < from_pos);
496 assert!(from_pos < where_pos);
497 assert!(where_pos < order_pos);
498 assert!(order_pos < limit_pos);
499 }
500
501 #[test]
502 fn test_find_first_table_name() {
503 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine);
504 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
505
506 assert!(sql.contains("test_models"));
507 }
508
509 #[tokio::test]
512 async fn test_find_first_exec() {
513 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
514 .r#where(Filter::Equals("id".into(), FilterValue::Int(1)));
515
516 let result = op.exec().await;
517
518 assert!(result.is_ok());
520 assert!(result.unwrap().is_none());
521 }
522
523 #[tokio::test]
524 async fn test_find_first_exec_required() {
525 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
526 .r#where(Filter::Equals("id".into(), FilterValue::Int(1)));
527
528 let result = op.exec_required().await;
529
530 assert!(result.is_err());
532 assert!(result.unwrap_err().is_not_found());
533 }
534
535 #[test]
538 fn test_find_first_full_chain() {
539 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
540 .r#where(Filter::Equals(
541 "status".into(),
542 FilterValue::String("active".to_string()),
543 ))
544 .order_by(OrderByField::desc("created_at"))
545 .select(Select::fields(["id", "name", "email"]));
546
547 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
548
549 assert!(sql.contains("SELECT id, name, email FROM"));
550 assert!(sql.contains("WHERE"));
551 assert!(sql.contains("ORDER BY created_at DESC"));
552 assert!(sql.contains("LIMIT 1"));
553 assert_eq!(params.len(), 1);
554 }
555
556 #[test]
559 fn test_find_first_with_like_filter() {
560 let op =
561 FindFirstOperation::<MockEngine, TestModel>::new(MockEngine).r#where(Filter::Contains(
562 "email".into(),
563 FilterValue::String("@example.com".to_string()),
564 ));
565
566 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
567
568 assert!(sql.contains("LIKE"));
569 assert_eq!(params.len(), 1);
570 }
571
572 #[test]
573 fn test_find_first_with_null_filter() {
574 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine)
575 .r#where(Filter::IsNull("deleted_at".into()));
576
577 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
578
579 assert!(sql.contains("IS NULL"));
580 assert!(params.is_empty());
581 }
582
583 #[test]
584 fn test_find_first_with_not_filter() {
585 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine).r#where(Filter::Not(
586 Box::new(Filter::Equals(
587 "status".into(),
588 FilterValue::String("deleted".to_string()),
589 )),
590 ));
591
592 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
593
594 assert!(sql.contains("NOT"));
595 assert_eq!(params.len(), 1);
596 }
597
598 #[test]
599 fn test_find_first_with_in_filter() {
600 let op = FindFirstOperation::<MockEngine, TestModel>::new(MockEngine).r#where(Filter::In(
601 "status".into(),
602 vec![
603 FilterValue::String("pending".to_string()),
604 FilterValue::String("processing".to_string()),
605 ],
606 ));
607
608 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
609
610 assert!(sql.contains("IN"));
611 assert_eq!(params.len(), 2);
612 }
613}