Skip to main content

sea_orm/database/
mock.rs

1use crate::{
2    DatabaseConnection, DatabaseConnectionType, DbBackend, EntityTrait, ExecResult,
3    ExecResultHolder, Iden, IdenStatic, Iterable, MockDatabaseConnection, MockDatabaseTrait,
4    ModelTrait, QueryResult, QueryResultRow, SelectA, SelectB, Statement, error::*,
5};
6use sea_query::{Value, ValueType, Values};
7use std::{collections::BTreeMap, sync::Arc};
8use tracing::instrument;
9
10/// Scripted in-memory database for unit tests.
11///
12/// Queue up exec and query results with [`append_exec_results`](Self::append_exec_results)
13/// / [`append_query_results`](Self::append_query_results), call
14/// [`into_connection`](Self::into_connection) to get a [`DatabaseConnection`]
15/// you can pass to your code, then inspect what was executed via
16/// [`into_transaction_log`](crate::MockDatabaseConnection::into_transaction_log)
17/// on the connection.
18#[derive(Debug)]
19pub struct MockDatabase {
20    db_backend: DbBackend,
21    transaction: Option<OpenTransaction>,
22    transaction_log: Vec<Transaction>,
23    exec_results: Vec<Result<MockExecResult, DbErr>>,
24    query_results: Vec<Result<Vec<MockRow>, DbErr>>,
25}
26
27/// Canned [`ExecResult`](crate::ExecResult)-equivalent returned by a
28/// [`MockDatabase`] for non-`SELECT` statements.
29#[derive(Clone, Debug, Default)]
30pub struct MockExecResult {
31    /// Value reported by [`ExecResult::last_insert_id`](crate::ExecResult::last_insert_id).
32    pub last_insert_id: u64,
33    /// Value reported by [`ExecResult::rows_affected`](crate::ExecResult::rows_affected).
34    pub rows_affected: u64,
35}
36
37/// A single canned row returned by a [`MockDatabase`] — a name → value map
38/// matching the columns the query selected.
39#[derive(Clone, Debug)]
40pub struct MockRow {
41    /// Cell values keyed by column name.
42    pub(crate) values: BTreeMap<String, Value>,
43}
44
45/// Conversion into a [`MockRow`]. Implemented for `Model` (via
46/// `#[derive(DeriveModel)]`) and for `BTreeMap<String, Value>` so you can
47/// hand either to [`MockDatabase::append_query_results`].
48pub trait IntoMockRow {
49    /// Build the row.
50    fn into_mock_row(self) -> MockRow;
51}
52
53/// An in-progress transaction recorded by a [`MockDatabase`]. Once committed
54/// or rolled back, it becomes a [`Transaction`] in the transaction log.
55#[derive(Debug)]
56pub struct OpenTransaction {
57    stmts: Vec<Statement>,
58    transaction_depth: usize,
59}
60
61/// A completed transaction recorded by a [`MockDatabase`] — the ordered list
62/// of statements executed against it. Compare against expected SQL in tests
63/// via [`Transaction::from_sql_and_values`](crate::Transaction::from_sql_and_values).
64#[derive(Debug, Clone, PartialEq)]
65pub struct Transaction {
66    stmts: Vec<Statement>,
67}
68
69impl MockDatabase {
70    /// Instantiate a mock database with a [DbBackend] to simulate real
71    /// world SQL databases
72    pub fn new(db_backend: DbBackend) -> Self {
73        Self {
74            db_backend,
75            transaction: None,
76            transaction_log: Vec::new(),
77            exec_results: Vec::new(),
78            query_results: Vec::new(),
79        }
80    }
81
82    /// Create a database connection
83    pub fn into_connection(self) -> DatabaseConnection {
84        DatabaseConnectionType::MockDatabaseConnection(Arc::new(MockDatabaseConnection::new(self)))
85            .into()
86    }
87
88    /// Add some [MockExecResult]s to `exec_results`
89    pub fn append_exec_results<I>(mut self, vec: I) -> Self
90    where
91        I: IntoIterator<Item = MockExecResult>,
92    {
93        self.exec_results.extend(vec.into_iter().map(Result::Ok));
94        self
95    }
96
97    /// Add some Values to `query_results`
98    pub fn append_query_results<T, I, II>(mut self, vec: II) -> Self
99    where
100        T: IntoMockRow,
101        I: IntoIterator<Item = T>,
102        II: IntoIterator<Item = I>,
103    {
104        for row in vec.into_iter() {
105            let row = row.into_iter().map(|vec| Ok(vec.into_mock_row())).collect();
106            self.query_results.push(row);
107        }
108        self
109    }
110
111    /// Add some [DbErr]s to `exec_results`
112    pub fn append_exec_errors<I>(mut self, vec: I) -> Self
113    where
114        I: IntoIterator<Item = DbErr>,
115    {
116        self.exec_results.extend(vec.into_iter().map(Result::Err));
117        self
118    }
119
120    /// Add some [DbErr]s to `query_results`
121    pub fn append_query_errors<I>(mut self, vec: I) -> Self
122    where
123        I: IntoIterator<Item = DbErr>,
124    {
125        self.query_results.extend(vec.into_iter().map(Result::Err));
126        self
127    }
128}
129
130impl MockDatabaseTrait for MockDatabase {
131    #[instrument(level = "trace", skip(statement))]
132    fn execute(&mut self, counter: usize, statement: Statement) -> Result<ExecResult, DbErr> {
133        if let Some(transaction) = &mut self.transaction {
134            transaction.push(statement);
135        } else {
136            self.transaction_log.push(Transaction::one(statement));
137        }
138        if counter < self.exec_results.len() {
139            match std::mem::replace(
140                &mut self.exec_results[counter],
141                Err(exec_err("this value has been consumed already")),
142            ) {
143                Ok(result) => Ok(ExecResult {
144                    result: ExecResultHolder::Mock(result),
145                }),
146                Err(err) => Err(err),
147            }
148        } else {
149            Err(exec_err("`exec_results` buffer is empty"))
150        }
151    }
152
153    #[instrument(level = "trace", skip(statement))]
154    fn query(&mut self, counter: usize, statement: Statement) -> Result<Vec<QueryResult>, DbErr> {
155        if let Some(transaction) = &mut self.transaction {
156            transaction.push(statement);
157        } else {
158            self.transaction_log.push(Transaction::one(statement));
159        }
160        if counter < self.query_results.len() {
161            match std::mem::replace(
162                &mut self.query_results[counter],
163                Err(query_err("this value has been consumed already")),
164            ) {
165                Ok(result) => Ok(result
166                    .into_iter()
167                    .map(|row| QueryResult {
168                        row: QueryResultRow::Mock(row),
169                    })
170                    .collect()),
171                Err(err) => Err(err),
172            }
173        } else {
174            Err(query_err("`query_results` buffer is empty."))
175        }
176    }
177
178    #[instrument(level = "trace")]
179    fn begin(&mut self) {
180        match self.transaction.as_mut() {
181            Some(transaction) => transaction.begin_nested(self.db_backend),
182            None => self.transaction = Some(OpenTransaction::init()),
183        }
184    }
185
186    #[instrument(level = "trace")]
187    fn commit(&mut self) {
188        match self.transaction.as_mut() {
189            Some(transaction) => {
190                if transaction.commit(self.db_backend) {
191                    if let Some(transaction) = self.transaction.take() {
192                        self.transaction_log.push(transaction.into_transaction());
193                    }
194                }
195            }
196            None => panic!("There is no open transaction to commit"),
197        }
198    }
199
200    #[instrument(level = "trace")]
201    fn rollback(&mut self) {
202        match self.transaction.as_mut() {
203            Some(transaction) => {
204                if transaction.rollback(self.db_backend) {
205                    if let Some(transaction) = self.transaction.take() {
206                        self.transaction_log.push(transaction.into_transaction());
207                    }
208                }
209            }
210            None => panic!("There is no open transaction to rollback"),
211        }
212    }
213
214    fn drain_transaction_log(&mut self) -> Vec<Transaction> {
215        std::mem::take(&mut self.transaction_log)
216    }
217
218    fn get_database_backend(&self) -> DbBackend {
219        self.db_backend
220    }
221
222    fn ping(&self) -> Result<(), DbErr> {
223        Ok(())
224    }
225}
226
227impl MockRow {
228    /// Get a value from the [MockRow]
229    pub fn try_get<T, I: crate::ColIdx>(&self, index: I) -> Result<T, DbErr>
230    where
231        T: ValueType,
232    {
233        if let Some(index) = index.as_str() {
234            T::try_from(
235                self.values
236                    .get(index)
237                    .ok_or_else(|| query_err(format!("No column for ColIdx {index:?}")))?
238                    .clone(),
239            )
240            .map_err(type_err)
241        } else if let Some(index) = index.as_usize() {
242            let (_, value) = self
243                .values
244                .iter()
245                .nth(*index)
246                .ok_or_else(|| query_err(format!("Column at index {index} not found")))?;
247            T::try_from(value.clone()).map_err(type_err)
248        } else {
249            unreachable!("Missing ColIdx implementation for MockRow");
250        }
251    }
252
253    /// An iterator over the keys and values of a mock row
254    pub fn into_column_value_tuples(self) -> impl Iterator<Item = (String, Value)> {
255        self.values.into_iter()
256    }
257}
258
259impl IntoMockRow for MockRow {
260    fn into_mock_row(self) -> MockRow {
261        self
262    }
263}
264
265impl<M> IntoMockRow for M
266where
267    M: ModelTrait,
268{
269    fn into_mock_row(self) -> MockRow {
270        let mut values = BTreeMap::new();
271        for col in <<M::Entity as EntityTrait>::Column>::iter() {
272            values.insert(col.to_string(), self.get(col));
273        }
274        MockRow { values }
275    }
276}
277
278impl<M, N> IntoMockRow for (M, N)
279where
280    M: ModelTrait,
281    N: ModelTrait,
282{
283    fn into_mock_row(self) -> MockRow {
284        let mut mapped_join = BTreeMap::new();
285
286        for column in <<M as ModelTrait>::Entity as EntityTrait>::Column::iter() {
287            mapped_join.insert(
288                format!("{}{}", SelectA.as_str(), column.as_str()),
289                self.0.get(column),
290            );
291        }
292        for column in <<N as ModelTrait>::Entity as EntityTrait>::Column::iter() {
293            mapped_join.insert(
294                format!("{}{}", SelectB.as_str(), column.as_str()),
295                self.1.get(column),
296            );
297        }
298
299        mapped_join.into_mock_row()
300    }
301}
302
303impl<M, N> IntoMockRow for (M, Option<N>)
304where
305    M: ModelTrait,
306    N: ModelTrait,
307{
308    fn into_mock_row(self) -> MockRow {
309        let mut mapped_join = BTreeMap::new();
310
311        for column in <<M as ModelTrait>::Entity as EntityTrait>::Column::iter() {
312            mapped_join.insert(
313                format!("{}{}", SelectA.as_str(), column.as_str()),
314                self.0.get(column),
315            );
316        }
317        if let Some(b_entity) = self.1 {
318            for column in <<N as ModelTrait>::Entity as EntityTrait>::Column::iter() {
319                mapped_join.insert(
320                    format!("{}{}", SelectB.as_str(), column.as_str()),
321                    b_entity.get(column),
322                );
323            }
324        }
325
326        mapped_join.into_mock_row()
327    }
328}
329
330impl<T> IntoMockRow for BTreeMap<T, Value>
331where
332    T: Into<String>,
333{
334    fn into_mock_row(self) -> MockRow {
335        MockRow {
336            values: self.into_iter().map(|(k, v)| (k.into(), v)).collect(),
337        }
338    }
339}
340
341impl Transaction {
342    /// Get the [Value]s from s raw SQL statement depending on the [DatabaseBackend](crate::DatabaseBackend)
343    pub fn from_sql_and_values<I, T>(db_backend: DbBackend, sql: T, values: I) -> Self
344    where
345        I: IntoIterator<Item = Value>,
346        T: Into<String>,
347    {
348        Self::one(Statement::from_string_values_tuple(
349            db_backend,
350            (sql, Values(values.into_iter().collect())),
351        ))
352    }
353
354    /// Create a Transaction with one statement
355    pub fn one(stmt: Statement) -> Self {
356        Self { stmts: vec![stmt] }
357    }
358
359    /// Create a Transaction with many statements
360    pub fn many<I>(stmts: I) -> Self
361    where
362        I: IntoIterator<Item = Statement>,
363    {
364        Self {
365            stmts: stmts.into_iter().collect(),
366        }
367    }
368
369    /// Wrap each Statement as a single-statement Transaction
370    pub fn wrap<I>(stmts: I) -> Vec<Self>
371    where
372        I: IntoIterator<Item = Statement>,
373    {
374        stmts.into_iter().map(Self::one).collect()
375    }
376
377    /// Get the list of statements
378    pub fn statements(&self) -> &[Statement] {
379        &self.stmts
380    }
381}
382
383impl OpenTransaction {
384    fn init() -> Self {
385        Self {
386            stmts: vec![Statement::from_string(DbBackend::Postgres, "BEGIN")],
387            transaction_depth: 0,
388        }
389    }
390
391    fn begin_nested(&mut self, db_backend: DbBackend) {
392        self.transaction_depth += 1;
393        self.push(Statement::from_string(
394            db_backend,
395            format!("SAVEPOINT savepoint_{}", self.transaction_depth),
396        ));
397    }
398
399    fn commit(&mut self, db_backend: DbBackend) -> bool {
400        if self.transaction_depth == 0 {
401            self.push(Statement::from_string(db_backend, "COMMIT"));
402            true
403        } else {
404            self.push(Statement::from_string(
405                db_backend,
406                format!("RELEASE SAVEPOINT savepoint_{}", self.transaction_depth),
407            ));
408            self.transaction_depth -= 1;
409            false
410        }
411    }
412
413    fn rollback(&mut self, db_backend: DbBackend) -> bool {
414        if self.transaction_depth == 0 {
415            self.push(Statement::from_string(db_backend, "ROLLBACK"));
416            true
417        } else {
418            self.push(Statement::from_string(
419                db_backend,
420                format!("ROLLBACK TO SAVEPOINT savepoint_{}", self.transaction_depth),
421            ));
422            self.transaction_depth -= 1;
423            false
424        }
425    }
426
427    fn push(&mut self, stmt: Statement) {
428        self.stmts.push(stmt);
429    }
430
431    fn into_transaction(self) -> Transaction {
432        match self.transaction_depth {
433            0 => Transaction { stmts: self.stmts },
434            _ => panic!("There is uncommitted nested transaction"),
435        }
436    }
437}
438
439#[cfg(test)]
440#[cfg(feature = "mock")]
441mod tests {
442    #[cfg(feature = "sync")]
443    use crate::util::StreamShim;
444    use crate::{
445        DbBackend, DbErr, IntoMockRow, MockDatabase, Statement, Transaction, TransactionError,
446        TransactionTrait, entity::*, error::*, tests_cfg::*,
447    };
448    // In the sync variant `StreamShim` provides `try_next`; `futures_util` isn't a dependency there.
449    #[cfg(not(feature = "sync"))]
450    use futures_util::TryStreamExt;
451    use pretty_assertions::assert_eq;
452
453    #[derive(Debug, PartialEq, Eq)]
454    pub struct MyErr(String);
455
456    impl std::error::Error for MyErr {}
457
458    impl std::fmt::Display for MyErr {
459        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
460            write!(f, "{}", self.0.as_str())
461        }
462    }
463
464    #[smol_potat::test]
465    async fn test_transaction_1() {
466        let db = MockDatabase::new(DbBackend::Postgres).into_connection();
467
468        db.transaction_async::<_, (), DbErr>(async |txn| {
469            let _1 = cake::Entity::find().one(txn).await;
470            let _2 = fruit::Entity::find().all(txn).await;
471
472            Ok(())
473        })
474        .await
475        .unwrap();
476
477        let _ = cake::Entity::find().all(&db).await;
478
479        assert_eq!(
480            db.into_transaction_log(),
481            [
482                Transaction::many([
483                    Statement::from_string(DbBackend::Postgres, "BEGIN"),
484                    Statement::from_sql_and_values(
485                        DbBackend::Postgres,
486                        r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
487                        [1u64.into()]
488                    ),
489                    Statement::from_sql_and_values(
490                        DbBackend::Postgres,
491                        r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
492                        []
493                    ),
494                    Statement::from_string(DbBackend::Postgres, "COMMIT"),
495                ]),
496                Transaction::from_sql_and_values(
497                    DbBackend::Postgres,
498                    r#"SELECT "cake"."id", "cake"."name" FROM "cake""#,
499                    []
500                ),
501            ]
502        );
503    }
504
505    #[smol_potat::test]
506    async fn test_transaction_2() {
507        let db = MockDatabase::new(DbBackend::Postgres).into_connection();
508
509        let result = db
510            .transaction_async::<_, (), MyErr>(async |txn| {
511                let _ = cake::Entity::find().one(txn).await;
512                Err(MyErr("test".to_owned()))
513            })
514            .await;
515
516        match result {
517            Err(TransactionError::Transaction(err)) => {
518                assert_eq!(err, MyErr("test".to_owned()))
519            }
520            _ => unreachable!(),
521        }
522
523        assert_eq!(
524            db.into_transaction_log(),
525            [Transaction::many([
526                Statement::from_string(DbBackend::Postgres, "BEGIN"),
527                Statement::from_sql_and_values(
528                    DbBackend::Postgres,
529                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
530                    [1u64.into()]
531                ),
532                Statement::from_string(DbBackend::Postgres, "ROLLBACK"),
533            ])]
534        );
535    }
536
537    #[smol_potat::test]
538    async fn test_nested_transaction_1() {
539        let db = MockDatabase::new(DbBackend::Postgres).into_connection();
540
541        db.transaction_async::<_, (), DbErr>(async |txn| {
542            let _ = cake::Entity::find().one(txn).await;
543
544            txn.transaction_async::<_, (), DbErr>(async |txn| {
545                let _ = fruit::Entity::find().all(txn).await;
546
547                Ok(())
548            })
549            .await
550            .unwrap();
551
552            Ok(())
553        })
554        .await
555        .unwrap();
556
557        assert_eq!(
558            db.into_transaction_log(),
559            [Transaction::many([
560                Statement::from_string(DbBackend::Postgres, "BEGIN"),
561                Statement::from_sql_and_values(
562                    DbBackend::Postgres,
563                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
564                    [1u64.into()]
565                ),
566                Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"),
567                Statement::from_sql_and_values(
568                    DbBackend::Postgres,
569                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
570                    []
571                ),
572                Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"),
573                Statement::from_string(DbBackend::Postgres, "COMMIT"),
574            ]),]
575        );
576    }
577
578    #[smol_potat::test]
579    async fn test_nested_transaction_2() {
580        let db = MockDatabase::new(DbBackend::Postgres).into_connection();
581
582        db.transaction_async::<_, (), DbErr>(async |txn| {
583            let _ = cake::Entity::find().one(txn).await;
584
585            txn.transaction_async::<_, (), DbErr>(async |txn| {
586                let _ = fruit::Entity::find().all(txn).await;
587
588                txn.transaction_async::<_, (), DbErr>(async |txn| {
589                    let _ = cake::Entity::find().all(txn).await;
590
591                    Ok(())
592                })
593                .await
594                .unwrap();
595
596                Ok(())
597            })
598            .await
599            .unwrap();
600
601            Ok(())
602        })
603        .await
604        .unwrap();
605
606        assert_eq!(
607            db.into_transaction_log(),
608            [Transaction::many([
609                Statement::from_string(DbBackend::Postgres, "BEGIN"),
610                Statement::from_sql_and_values(
611                    DbBackend::Postgres,
612                    r#"SELECT "cake"."id", "cake"."name" FROM "cake" LIMIT $1"#,
613                    [1u64.into()]
614                ),
615                Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_1"),
616                Statement::from_sql_and_values(
617                    DbBackend::Postgres,
618                    r#"SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit""#,
619                    []
620                ),
621                Statement::from_string(DbBackend::Postgres, "SAVEPOINT savepoint_2"),
622                Statement::from_sql_and_values(
623                    DbBackend::Postgres,
624                    r#"SELECT "cake"."id", "cake"."name" FROM "cake""#,
625                    []
626                ),
627                Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_2"),
628                Statement::from_string(DbBackend::Postgres, "RELEASE SAVEPOINT savepoint_1"),
629                Statement::from_string(DbBackend::Postgres, "COMMIT"),
630            ]),]
631        );
632    }
633
634    #[smol_potat::test]
635    #[cfg(feature = "stream")]
636    async fn test_stream_1() -> Result<(), DbErr> {
637        let apple = fruit::Model {
638            id: 1,
639            name: "Apple".to_owned(),
640            cake_id: Some(1),
641        };
642
643        let orange = fruit::Model {
644            id: 2,
645            name: "orange".to_owned(),
646            cake_id: None,
647        };
648
649        let db = MockDatabase::new(DbBackend::Postgres)
650            .append_query_results([[apple.clone(), orange.clone()]])
651            .into_connection();
652
653        let mut stream = fruit::Entity::find().stream(&db).await?;
654
655        assert_eq!(stream.try_next().await?, Some(apple));
656
657        assert_eq!(stream.try_next().await?, Some(orange));
658
659        assert_eq!(stream.try_next().await?, None);
660
661        Ok(())
662    }
663
664    #[smol_potat::test]
665    #[cfg(feature = "stream")]
666    async fn test_stream_2() -> Result<(), DbErr> {
667        use fruit::Entity as Fruit;
668        let db = MockDatabase::new(DbBackend::Postgres)
669            .append_query_results([Vec::<fruit::Model>::new()])
670            .into_connection();
671
672        let mut stream = Fruit::find().stream(&db).await?;
673
674        while let Some(item) = stream.try_next().await? {
675            let _item: fruit::ActiveModel = item.into();
676        }
677
678        Ok(())
679    }
680
681    #[smol_potat::test]
682    #[cfg(feature = "stream")]
683    async fn test_stream_in_transaction() -> Result<(), DbErr> {
684        let apple = fruit::Model {
685            id: 1,
686            name: "Apple".to_owned(),
687            cake_id: Some(1),
688        };
689
690        let orange = fruit::Model {
691            id: 2,
692            name: "orange".to_owned(),
693            cake_id: None,
694        };
695
696        let db = MockDatabase::new(DbBackend::Postgres)
697            .append_query_results([[apple.clone(), orange.clone()]])
698            .into_connection();
699
700        let txn = db.begin().await?;
701
702        if let Ok(mut stream) = fruit::Entity::find().stream(&txn).await {
703            assert_eq!(stream.try_next().await?, Some(apple));
704
705            assert_eq!(stream.try_next().await?, Some(orange));
706
707            assert_eq!(stream.try_next().await?, None);
708
709            // stream will be dropped end of scope
710        }
711
712        txn.commit().await?;
713
714        Ok(())
715    }
716
717    #[smol_potat::test]
718    async fn test_mocked_join() {
719        let row = (
720            cake::Model {
721                id: 1,
722                name: "Apple Cake".to_owned(),
723            },
724            fruit::Model {
725                id: 2,
726                name: "Apple".to_owned(),
727                cake_id: Some(1),
728            },
729        );
730        let mocked_row = row.into_mock_row();
731
732        let a_id = mocked_row.try_get::<i32, _>("A_id");
733        assert!(a_id.is_ok());
734        assert_eq!(1, a_id.unwrap());
735        let b_id = mocked_row.try_get::<i32, _>("B_id");
736        assert!(b_id.is_ok());
737        assert_eq!(2, b_id.unwrap());
738    }
739
740    #[smol_potat::test]
741    async fn test_find_also_related_1() -> Result<(), DbErr> {
742        let db = MockDatabase::new(DbBackend::Postgres)
743            .append_query_results([[(
744                cake::Model {
745                    id: 1,
746                    name: "Apple Cake".to_owned(),
747                },
748                fruit::Model {
749                    id: 2,
750                    name: "Apple".to_owned(),
751                    cake_id: Some(1),
752                },
753            )]])
754            .into_connection();
755
756        assert_eq!(
757            cake::Entity::find()
758                .find_also_related(fruit::Entity)
759                .all(&db)
760                .await?,
761            [(
762                cake::Model {
763                    id: 1,
764                    name: "Apple Cake".to_owned(),
765                },
766                Some(fruit::Model {
767                    id: 2,
768                    name: "Apple".to_owned(),
769                    cake_id: Some(1),
770                })
771            )]
772        );
773
774        assert_eq!(
775            db.into_transaction_log(),
776            [Transaction::from_sql_and_values(
777                DbBackend::Postgres,
778                r#"SELECT "cake"."id" AS "A_id", "cake"."name" AS "A_name", "fruit"."id" AS "B_id", "fruit"."name" AS "B_name", "fruit"."cake_id" AS "B_cake_id" FROM "cake" LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id""#,
779                []
780            ),]
781        );
782
783        Ok(())
784    }
785
786    #[cfg(feature = "postgres-array")]
787    #[smol_potat::test]
788    async fn test_postgres_array_1() -> Result<(), DbErr> {
789        mod collection {
790            use crate as sea_orm;
791            use crate::entity::prelude::*;
792
793            #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
794            #[sea_orm(table_name = "collection")]
795            pub struct Model {
796                #[sea_orm(primary_key)]
797                pub id: i32,
798                pub integers: Vec<i32>,
799                pub integers_opt: Option<Vec<i32>>,
800            }
801
802            #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
803            pub enum Relation {}
804
805            impl ActiveModelBehavior for ActiveModel {}
806        }
807
808        let db = MockDatabase::new(DbBackend::Postgres)
809            .append_query_results([[
810                collection::Model {
811                    id: 1,
812                    integers: vec![1, 2, 3],
813                    integers_opt: Some(vec![1, 2, 3]),
814                },
815                collection::Model {
816                    id: 2,
817                    integers: vec![],
818                    integers_opt: Some(vec![]),
819                },
820                collection::Model {
821                    id: 3,
822                    integers: vec![3, 1, 4],
823                    integers_opt: None,
824                },
825            ]])
826            .into_connection();
827
828        assert_eq!(
829            collection::Entity::find().all(&db).await?,
830            [
831                collection::Model {
832                    id: 1,
833                    integers: vec![1, 2, 3],
834                    integers_opt: Some(vec![1, 2, 3]),
835                },
836                collection::Model {
837                    id: 2,
838                    integers: vec![],
839                    integers_opt: Some(vec![]),
840                },
841                collection::Model {
842                    id: 3,
843                    integers: vec![3, 1, 4],
844                    integers_opt: None,
845                },
846            ]
847        );
848
849        assert_eq!(
850            db.into_transaction_log(),
851            [Transaction::from_sql_and_values(
852                DbBackend::Postgres,
853                r#"SELECT "collection"."id", "collection"."integers", "collection"."integers_opt" FROM "collection""#,
854                []
855            ),]
856        );
857
858        Ok(())
859    }
860
861    #[smol_potat::test]
862    async fn test_query_err() {
863        let db = MockDatabase::new(DbBackend::MySql)
864            .append_query_errors([query_err("this is a mock query error")])
865            .into_connection();
866
867        assert_eq!(
868            cake::Entity::find().all(&db).await,
869            Err(query_err("this is a mock query error"))
870        );
871    }
872
873    #[smol_potat::test]
874    async fn test_exec_err() {
875        let db = MockDatabase::new(DbBackend::MySql)
876            .append_exec_errors([exec_err("this is a mock exec error")])
877            .into_connection();
878
879        let model = cake::ActiveModel::new();
880
881        assert_eq!(
882            model.save(&db).await,
883            Err(exec_err("this is a mock exec error"))
884        );
885    }
886}