1use std::sync::{Arc, Mutex, MutexGuard};
2
3use sofie_core::{Error, Result, RowReader, SqlValue, unpoisoned};
4use sofie_db::{BoxFuture, Dialect, Driver, MessageSql, Migrations, RowSink, Step};
5
6#[cfg(feature = "contracts")]
8pub fn fake<G: ?Sized + Send + Sync + 'static>(app: &crate::TestApp, fake: Arc<G>) {
9 sofie_db::provide::<G>(app.db(), fake);
10}
11
12#[derive(Clone, Default)]
13pub struct FakeDriver {
14 record: Arc<Mutex<Record>>,
15 rows: Arc<Vec<Vec<SqlValue>>>,
16 is_down: bool,
17}
18
19#[derive(Default)]
20struct Record {
21 queries: Vec<(String, Vec<SqlValue>)>,
22 transactions: Vec<Vec<Step>>,
23}
24
25impl FakeDriver {
26 #[must_use]
27 pub fn returning(rows: Vec<Vec<SqlValue>>) -> Self {
28 Self {
29 rows: Arc::new(rows),
30 ..Self::default()
31 }
32 }
33
34 #[must_use]
35 pub fn down() -> Self {
36 Self {
37 is_down: true,
38 ..Self::default()
39 }
40 }
41
42 #[must_use]
43 pub fn queries(&self) -> Vec<(String, Vec<SqlValue>)> {
44 self.record().queries.clone()
45 }
46
47 #[must_use]
48 pub fn transaction_count(&self) -> usize {
49 self.record().transactions.len()
50 }
51
52 #[must_use]
53 pub fn take_transactions(&self) -> Vec<Vec<Step>> {
54 std::mem::take(&mut self.record().transactions)
55 }
56
57 fn record(&self) -> MutexGuard<'_, Record> {
58 unpoisoned::lock(&self.record)
59 }
60
61 fn outcome(&self) -> Result<()> {
62 if self.is_down {
63 return Err(Error::internal_message("connection refused"));
64 }
65 Ok(())
66 }
67}
68
69impl Driver for FakeDriver {
70 fn dialect(&self) -> &dyn Dialect {
71 &FakeDialect
72 }
73
74 fn query<'a>(
75 &'a self,
76 sql: &'a str,
77 params: &'a [SqlValue],
78 rows: &'a mut dyn RowSink,
79 ) -> BoxFuture<'a, Result<()>> {
80 self.record()
81 .queries
82 .push((sql.to_owned(), params.to_vec()));
83 let answer = self.outcome().and_then(|()| {
84 self.rows
85 .iter()
86 .try_for_each(|values| rows.row(&mut RowReader::new(values)))
87 });
88 Box::pin(std::future::ready(answer))
89 }
90
91 fn transaction(&self, steps: Vec<Step>) -> BoxFuture<'_, Result<()>> {
92 self.record().transactions.push(steps);
93 Box::pin(std::future::ready(self.outcome()))
94 }
95}
96
97struct FakeDialect;
98
99const NO_MESSAGES: MessageSql = MessageSql {
100 insert: "",
101 fan_out: "",
102 fan_out_events: "",
103 claim: "",
104 renew: "",
105 complete: "",
106 retry: "",
107 bury: "",
108 to_job: "",
109 add_schedule: "",
110 fire_schedule: "",
111 notify: None,
112};
113
114impl Dialect for FakeDialect {
115 fn push_placeholder(&self, sql: &mut String, index: usize) {
116 sql.push('$');
117 sql.push_str(&index.to_string());
118 }
119
120 fn migrations_setup(&self) -> &'static str {
121 ""
122 }
123
124 fn migrations_lock(&self) -> &'static str {
125 ""
126 }
127
128 fn enter_schema(&self, _schema: &str) -> String {
129 String::new()
130 }
131
132 fn framework_migrations(&self) -> Migrations {
133 Migrations::new("sofie", &[])
134 }
135
136 fn messages(&self) -> &'static MessageSql {
137 &NO_MESSAGES
138 }
139}