1use std::collections::HashMap;
2
3use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, FromQueryResult, Iterable};
4use serde_json::{Value, json};
5
6use crate::{Record, RecordError, Relation};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum OrderDirection {
11 Asc,
13 Desc,
15}
16
17#[allow(async_fn_in_trait, dead_code)]
19pub(crate) trait AsyncQuerying: Record {
20 async fn find(id: i64, db: &DatabaseConnection) -> Result<Self, RecordError>
22 where
23 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
24 {
25 Self::find_by_id(id, db).await?.ok_or(RecordError::NotFound)
26 }
27
28 async fn find_by_id(id: i64, db: &DatabaseConnection) -> Result<Option<Self>, RecordError>
30 where
31 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
32 {
33 let mut conditions = HashMap::new();
34 conditions.insert(Self::primary_key_name().to_owned(), json!(id));
35 Self::r#where(conditions).first(db).await
36 }
37
38 async fn find_by(
40 conditions: HashMap<String, Value>,
41 db: &DatabaseConnection,
42 ) -> Result<Option<Self>, RecordError>
43 where
44 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
45 {
46 Self::r#where(conditions).first(db).await
47 }
48
49 async fn find_by_bang(
51 conditions: HashMap<String, Value>,
52 db: &DatabaseConnection,
53 ) -> Result<Self, RecordError>
54 where
55 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
56 {
57 Self::find_by(conditions, db)
58 .await?
59 .ok_or(RecordError::NotFound)
60 }
61
62 async fn take(db: &DatabaseConnection) -> Result<Option<Self>, RecordError>
64 where
65 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
66 {
67 Relation::<Self>::new().first(db).await
68 }
69
70 async fn take_bang(db: &DatabaseConnection) -> Result<Self, RecordError>
72 where
73 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
74 {
75 Self::take(db).await?.ok_or(RecordError::NotFound)
76 }
77
78 async fn sole(db: &DatabaseConnection) -> Result<Self, RecordError>
80 where
81 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
82 {
83 Relation::<Self>::new().sole(db).await
84 }
85
86 async fn find_sole_by(
88 conditions: HashMap<String, Value>,
89 db: &DatabaseConnection,
90 ) -> Result<Self, RecordError>
91 where
92 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
93 {
94 Self::r#where(conditions).sole(db).await
95 }
96
97 async fn pluck(column: &str, db: &DatabaseConnection) -> Result<Vec<Value>, RecordError>
99 where
100 Self: serde::Serialize,
101 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
102 {
103 Relation::<Self>::new().pluck(column, db).await
104 }
105
106 async fn pick(column: &str, db: &DatabaseConnection) -> Result<Option<Value>, RecordError>
108 where
109 Self: serde::Serialize,
110 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
111 {
112 Relation::<Self>::new().pick(column, db).await
113 }
114
115 async fn ids(db: &DatabaseConnection) -> Result<Vec<i64>, RecordError>
117 where
118 Self: serde::Serialize,
119 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
120 {
121 Relation::<Self>::new().ids(db).await
122 }
123
124 async fn all(db: &DatabaseConnection) -> Result<Vec<Self>, RecordError>
126 where
127 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
128 {
129 Relation::<Self>::new().load(db).await
130 }
131
132 async fn first(db: &DatabaseConnection) -> Result<Option<Self>, RecordError>
134 where
135 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
136 {
137 Self::order(Self::primary_key_name(), OrderDirection::Asc)
138 .first(db)
139 .await
140 }
141
142 async fn last(db: &DatabaseConnection) -> Result<Option<Self>, RecordError>
144 where
145 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
146 {
147 Self::order(Self::primary_key_name(), OrderDirection::Desc)
148 .first(db)
149 .await
150 }
151
152 async fn count(db: &DatabaseConnection) -> Result<u64, RecordError>
154 where
155 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
156 <Self::Entity as EntityTrait>::Model: FromQueryResult + Send + Sync,
157 {
158 Relation::<Self>::new().count(db).await
159 }
160
161 async fn exists_with_conditions(
163 conditions: HashMap<String, Value>,
164 db: &DatabaseConnection,
165 ) -> Result<bool, RecordError>
166 where
167 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
168 {
169 Self::r#where(conditions).exists(db).await
170 }
171
172 async fn exists(
174 conditions: HashMap<String, Value>,
175 db: &DatabaseConnection,
176 ) -> Result<bool, RecordError>
177 where
178 <Self::Entity as EntityTrait>::Column: ColumnTrait + Iterable,
179 {
180 Self::exists_with_conditions(conditions, db).await
181 }
182
183 fn r#where(conditions: HashMap<String, Value>) -> Relation<Self> {
185 Relation::new().r#where(conditions)
186 }
187
188 fn order(column: &str, dir: OrderDirection) -> Relation<Self> {
190 Relation::new().order(column, dir)
191 }
192
193 fn limit(n: u64) -> Relation<Self> {
195 Relation::new().limit(n)
196 }
197
198 fn offset(n: u64) -> Relation<Self> {
200 Relation::new().offset(n)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use std::collections::HashMap;
207
208 use sea_orm::{ActiveModelTrait, ActiveValue::Set};
209 use serde_json::json;
210
211 use super::{AsyncQuerying, OrderDirection};
212 use crate::{
213 RecordError,
214 base::test_support::{TestUser, seed_users, setup_db, test_user},
215 };
216
217 #[tokio::test]
218 async fn find_returns_matching_record() {
219 let db = setup_db().await;
220 seed_users(&db).await;
221
222 let user = TestUser::find(2, &db)
223 .await
224 .expect("find should return a record");
225
226 assert_eq!(user.name, "Bob");
227 }
228
229 #[tokio::test]
230 async fn find_missing_returns_not_found() {
231 let db = setup_db().await;
232
233 let error = TestUser::find(404, &db)
234 .await
235 .expect_err("missing row should return an error");
236
237 assert!(matches!(error, RecordError::NotFound));
238 }
239
240 #[tokio::test]
241 async fn find_by_id_returns_none_when_missing() {
242 let db = setup_db().await;
243
244 let user = TestUser::find_by_id(404, &db)
245 .await
246 .expect("query should succeed");
247
248 assert!(user.is_none());
249 }
250
251 #[tokio::test]
252 async fn find_by_filters_by_conditions() {
253 let db = setup_db().await;
254 seed_users(&db).await;
255
256 let mut conditions = HashMap::new();
257 conditions.insert("email".to_owned(), json!("carol@example.com"));
258
259 let user = TestUser::find_by(conditions, &db)
260 .await
261 .expect("query should succeed")
262 .expect("row should exist");
263
264 assert_eq!(user.name, "Carol");
265 }
266
267 #[tokio::test]
268 async fn all_returns_every_row() {
269 let db = setup_db().await;
270 seed_users(&db).await;
271
272 let users = TestUser::all(&db).await.expect("all should succeed");
273
274 assert_eq!(users.len(), 3);
275 }
276
277 #[tokio::test]
278 async fn first_returns_lowest_primary_key() {
279 let db = setup_db().await;
280 seed_users(&db).await;
281
282 let user = TestUser::first(&db)
283 .await
284 .expect("query should succeed")
285 .expect("row should exist");
286
287 assert_eq!(user.name, "Alice");
288 }
289
290 #[tokio::test]
291 async fn last_returns_highest_primary_key() {
292 let db = setup_db().await;
293 seed_users(&db).await;
294
295 let user = TestUser::last(&db)
296 .await
297 .expect("query should succeed")
298 .expect("row should exist");
299
300 assert_eq!(user.name, "Carol");
301 }
302
303 #[tokio::test]
304 async fn count_returns_row_total() {
305 let db = setup_db().await;
306 seed_users(&db).await;
307
308 assert_eq!(TestUser::count(&db).await.expect("count should succeed"), 3);
309 }
310
311 #[tokio::test]
312 async fn exists_returns_true_for_matching_conditions() {
313 let db = setup_db().await;
314 seed_users(&db).await;
315
316 let mut conditions = HashMap::new();
317 conditions.insert("name".to_owned(), json!("Bob"));
318
319 assert!(
320 TestUser::exists(conditions, &db)
321 .await
322 .expect("exists should succeed")
323 );
324 }
325
326 #[tokio::test]
327 async fn exists_returns_false_for_missing_conditions() {
328 let db = setup_db().await;
329 seed_users(&db).await;
330
331 let mut conditions = HashMap::new();
332 conditions.insert("name".to_owned(), json!("Nobody"));
333
334 assert!(
335 !TestUser::exists(conditions, &db)
336 .await
337 .expect("exists should succeed")
338 );
339 }
340
341 #[tokio::test]
342 async fn query_builder_helpers_start_relations() {
343 let db = setup_db().await;
344 seed_users(&db).await;
345
346 let ordered = TestUser::order("id", OrderDirection::Desc)
347 .first(&db)
348 .await
349 .expect("query should succeed")
350 .expect("row should exist");
351 let limited = TestUser::limit(2)
352 .load(&db)
353 .await
354 .expect("limit should load");
355 let offset = TestUser::offset(2)
356 .order("id", OrderDirection::Asc)
357 .load(&db)
358 .await
359 .expect("offset should load");
360
361 assert_eq!(ordered.name, "Carol");
362 assert_eq!(limited.len(), 2);
363 assert_eq!(offset.len(), 1);
364 assert_eq!(offset[0].name, "Carol");
365 }
366 #[tokio::test]
367 async fn find_by_id_returns_matching_record_when_present() {
368 let db = setup_db().await;
369 seed_users(&db).await;
370
371 let user = TestUser::find_by_id(3, &db)
372 .await
373 .expect("query should succeed")
374 .expect("row should exist");
375
376 assert_eq!(user.name, "Carol");
377 }
378
379 #[tokio::test]
380 async fn find_by_returns_none_when_no_match_exists() {
381 let db = setup_db().await;
382 seed_users(&db).await;
383
384 let user = TestUser::find_by(
385 HashMap::from([("email".to_owned(), json!("missing@example.com"))]),
386 &db,
387 )
388 .await
389 .expect("query should succeed");
390
391 assert!(user.is_none());
392 }
393
394 #[tokio::test]
395 async fn find_by_with_multiple_conditions_requires_all_matches() {
396 let db = setup_db().await;
397 seed_users(&db).await;
398
399 let user = TestUser::find_by(
400 HashMap::from([
401 ("name".to_owned(), json!("Bob")),
402 ("email".to_owned(), json!("bob@example.com")),
403 ]),
404 &db,
405 )
406 .await
407 .expect("query should succeed")
408 .expect("row should exist");
409
410 assert_eq!(user.id, Some(2));
411 }
412
413 #[tokio::test]
414 async fn all_marks_loaded_rows_persisted() {
415 let db = setup_db().await;
416 seed_users(&db).await;
417
418 let users = TestUser::all(&db).await.expect("all should succeed");
419
420 assert!(
421 users
422 .iter()
423 .all(|user| user.state == crate::RecordState::Persisted)
424 );
425 }
426
427 #[tokio::test]
428 async fn first_returns_none_when_table_is_empty() {
429 let db = setup_db().await;
430
431 let user = TestUser::first(&db).await.expect("query should succeed");
432
433 assert!(user.is_none());
434 }
435
436 #[tokio::test]
437 async fn last_returns_none_when_table_is_empty() {
438 let db = setup_db().await;
439
440 let user = TestUser::last(&db).await.expect("query should succeed");
441
442 assert!(user.is_none());
443 }
444
445 #[tokio::test]
446 async fn count_returns_zero_when_table_is_empty() {
447 let db = setup_db().await;
448
449 assert_eq!(TestUser::count(&db).await.expect("count should succeed"), 0);
450 }
451
452 #[tokio::test]
453 async fn exists_with_empty_conditions_is_false_without_rows() {
454 let db = setup_db().await;
455
456 assert!(
457 !TestUser::exists(HashMap::new(), &db)
458 .await
459 .expect("exists should succeed")
460 );
461 }
462
463 #[tokio::test]
464 async fn exists_with_empty_conditions_is_true_when_rows_exist() {
465 let db = setup_db().await;
466 seed_users(&db).await;
467
468 assert!(
469 TestUser::exists(HashMap::new(), &db)
470 .await
471 .expect("exists should succeed")
472 );
473 }
474
475 #[tokio::test]
476 async fn exists_with_multiple_conditions_requires_all_conditions() {
477 let db = setup_db().await;
478 seed_users(&db).await;
479
480 assert!(
481 !TestUser::exists(
482 HashMap::from([
483 ("name".to_owned(), json!("Bob")),
484 ("email".to_owned(), json!("alice@example.com")),
485 ]),
486 &db,
487 )
488 .await
489 .expect("exists should succeed")
490 );
491 }
492
493 #[tokio::test]
494 async fn where_helper_loads_matching_rows() {
495 let db = setup_db().await;
496 seed_users(&db).await;
497
498 let users = TestUser::r#where(HashMap::from([("name".to_owned(), json!("Bob"))]))
499 .load(&db)
500 .await
501 .expect("where relation should load");
502
503 assert_eq!(users.len(), 1);
504 assert_eq!(users[0].email, "bob@example.com");
505 }
506
507 #[tokio::test]
508 async fn where_helper_with_multiple_conditions_loads_match() {
509 let db = setup_db().await;
510 seed_users(&db).await;
511
512 let users = TestUser::r#where(HashMap::from([
513 ("name".to_owned(), json!("Carol")),
514 ("email".to_owned(), json!("carol@example.com")),
515 ]))
516 .load(&db)
517 .await
518 .expect("where relation should load");
519
520 assert_eq!(users.len(), 1);
521 assert_eq!(users[0].id, Some(3));
522 }
523
524 #[tokio::test]
525 async fn where_helper_with_empty_conditions_loads_all_rows() {
526 let db = setup_db().await;
527 seed_users(&db).await;
528
529 let users = TestUser::r#where(HashMap::new())
530 .load(&db)
531 .await
532 .expect("where relation should load");
533
534 assert_eq!(users.len(), 3);
535 }
536
537 #[tokio::test]
538 async fn order_helper_descending_returns_expected_sequence() {
539 let db = setup_db().await;
540 seed_users(&db).await;
541
542 let users = TestUser::order("id", OrderDirection::Desc)
543 .load(&db)
544 .await
545 .expect("ordered relation should load");
546 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
547
548 assert_eq!(names, vec!["Carol", "Bob", "Alice"]);
549 }
550
551 #[tokio::test]
552 async fn order_helper_ascending_returns_expected_sequence() {
553 let db = setup_db().await;
554 seed_users(&db).await;
555
556 let users = TestUser::order("id", OrderDirection::Asc)
557 .load(&db)
558 .await
559 .expect("ordered relation should load");
560 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
561
562 assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
563 }
564
565 #[tokio::test]
566 async fn limit_helper_zero_returns_no_rows() {
567 let db = setup_db().await;
568 seed_users(&db).await;
569
570 let users = TestUser::limit(0)
571 .load(&db)
572 .await
573 .expect("limit should load");
574
575 assert!(users.is_empty());
576 }
577
578 #[tokio::test]
579 async fn limit_helper_can_chain_with_order() {
580 let db = setup_db().await;
581 seed_users(&db).await;
582
583 let users = TestUser::limit(2)
584 .order("id", OrderDirection::Desc)
585 .load(&db)
586 .await
587 .expect("relation should load");
588 let names = users.into_iter().map(|user| user.name).collect::<Vec<_>>();
589
590 assert_eq!(names, vec!["Carol", "Bob"]);
591 }
592
593 #[tokio::test]
594 async fn offset_helper_beyond_row_count_returns_empty() {
595 let db = setup_db().await;
596 seed_users(&db).await;
597
598 let users = TestUser::offset(10)
599 .order("id", OrderDirection::Asc)
600 .load(&db)
601 .await
602 .expect("relation should load");
603
604 assert!(users.is_empty());
605 }
606
607 #[tokio::test]
608 async fn offset_helper_can_chain_with_limit_and_order() {
609 let db = setup_db().await;
610 seed_users(&db).await;
611
612 let users = TestUser::offset(1)
613 .order("id", OrderDirection::Asc)
614 .limit(1)
615 .load(&db)
616 .await
617 .expect("relation should load");
618
619 assert_eq!(users.len(), 1);
620 assert_eq!(users[0].name, "Bob");
621 }
622
623 #[tokio::test]
624 async fn find_by_id_returns_persisted_record() {
625 let db = setup_db().await;
626 seed_users(&db).await;
627
628 let user = TestUser::find_by_id(1, &db)
629 .await
630 .expect("query should succeed")
631 .expect("row should exist");
632
633 assert_eq!(user.state, crate::RecordState::Persisted);
634 }
635
636 #[tokio::test]
637 async fn find_by_bang_returns_matching_record() {
638 let db = setup_db().await;
639 seed_users(&db).await;
640
641 let user = TestUser::find_by_bang(
642 HashMap::from([("email".to_owned(), json!("bob@example.com"))]),
643 &db,
644 )
645 .await
646 .expect("row should exist");
647
648 assert_eq!(user.name, "Bob");
649 }
650
651 #[tokio::test]
652 async fn find_by_bang_returns_not_found_when_missing() {
653 let db = setup_db().await;
654
655 let error = TestUser::find_by_bang(
656 HashMap::from([("email".to_owned(), json!("missing@example.com"))]),
657 &db,
658 )
659 .await
660 .expect_err("missing row should return an error");
661
662 assert!(matches!(error, RecordError::NotFound));
663 }
664
665 #[tokio::test]
666 async fn take_returns_first_available_row_without_order() {
667 let db = setup_db().await;
668 seed_users(&db).await;
669
670 let user = TestUser::take(&db)
671 .await
672 .expect("take should succeed")
673 .expect("row should exist");
674
675 assert_eq!(user.id, Some(1));
676 }
677
678 #[tokio::test]
679 async fn take_returns_none_when_table_is_empty() {
680 let db = setup_db().await;
681
682 let user = TestUser::take(&db).await.expect("take should succeed");
683
684 assert!(user.is_none());
685 }
686
687 #[tokio::test]
688 async fn take_bang_returns_not_found_when_table_is_empty() {
689 let db = setup_db().await;
690
691 let error = TestUser::take_bang(&db)
692 .await
693 .expect_err("empty tables should fail");
694
695 assert!(matches!(error, RecordError::NotFound));
696 }
697
698 #[tokio::test]
699 async fn sole_returns_only_row_when_table_has_one_record() {
700 let db = setup_db().await;
701 test_user::ActiveModel {
702 name: Set("Solo".to_owned()),
703 email: Set("solo@example.com".to_owned()),
704 ..Default::default()
705 }
706 .insert(&db)
707 .await
708 .expect("fixture insert should succeed");
709
710 let user = TestUser::sole(&db)
711 .await
712 .expect("sole should return the row");
713
714 assert_eq!(user.name, "Solo");
715 }
716
717 #[tokio::test]
718 async fn sole_returns_not_found_when_table_is_empty() {
719 let db = setup_db().await;
720
721 let error = TestUser::sole(&db)
722 .await
723 .expect_err("empty tables should fail");
724
725 assert!(matches!(error, RecordError::NotFound));
726 }
727
728 #[tokio::test]
729 async fn sole_returns_exceeded_when_multiple_rows_match() {
730 let db = setup_db().await;
731 seed_users(&db).await;
732
733 let error = TestUser::sole(&db)
734 .await
735 .expect_err("multiple rows should fail sole");
736
737 assert!(matches!(error, RecordError::SoleRecordExceeded));
738 }
739
740 #[tokio::test]
741 async fn find_sole_by_returns_matching_row() {
742 let db = setup_db().await;
743 seed_users(&db).await;
744
745 let user =
746 TestUser::find_sole_by(HashMap::from([("name".to_owned(), json!("Alice"))]), &db)
747 .await
748 .expect("single matching row should exist");
749
750 assert_eq!(user.email, "alice@example.com");
751 }
752
753 #[tokio::test]
754 async fn find_sole_by_returns_exceeded_when_multiple_rows_match() {
755 let db = setup_db().await;
756 seed_users(&db).await;
757 test_user::ActiveModel {
758 name: Set("Bob".to_owned()),
759 email: Set("bobby@example.com".to_owned()),
760 ..Default::default()
761 }
762 .insert(&db)
763 .await
764 .expect("fixture insert should succeed");
765
766 let error = TestUser::find_sole_by(HashMap::from([("name".to_owned(), json!("Bob"))]), &db)
767 .await
768 .expect_err("multiple matches should fail");
769
770 assert!(matches!(error, RecordError::SoleRecordExceeded));
771 }
772
773 #[tokio::test]
774 async fn pluck_returns_requested_column_values() {
775 let db = setup_db().await;
776 seed_users(&db).await;
777
778 let values = TestUser::pluck("name", &db)
779 .await
780 .expect("pluck should succeed");
781
782 assert_eq!(values, vec![json!("Alice"), json!("Bob"), json!("Carol")]);
783 }
784
785 #[tokio::test]
786 async fn pick_returns_first_requested_column_value() {
787 let db = setup_db().await;
788 seed_users(&db).await;
789
790 let value = TestUser::pick("email", &db)
791 .await
792 .expect("pick should succeed");
793
794 assert_eq!(value, Some(json!("alice@example.com")));
795 }
796
797 #[tokio::test]
798 async fn ids_returns_primary_keys() {
799 let db = setup_db().await;
800 seed_users(&db).await;
801
802 let ids = TestUser::ids(&db).await.expect("ids should succeed");
803
804 assert_eq!(ids, vec![1, 2, 3]);
805 }
806
807 #[tokio::test]
808 async fn exists_with_conditions_matches_rows() {
809 let db = setup_db().await;
810 seed_users(&db).await;
811
812 assert!(
813 TestUser::exists_with_conditions(
814 HashMap::from([("name".to_owned(), json!("Carol"))]),
815 &db,
816 )
817 .await
818 .expect("exists_with_conditions should succeed")
819 );
820 }
821
822 #[tokio::test]
823 async fn exists_with_conditions_returns_false_when_missing() {
824 let db = setup_db().await;
825
826 assert!(
827 !TestUser::exists_with_conditions(
828 HashMap::from([("name".to_owned(), json!("Nobody"))]),
829 &db,
830 )
831 .await
832 .expect("exists_with_conditions should succeed")
833 );
834 }
835}