1use std::sync::{Arc, Mutex};
13
14use rainier_orm::sea_query::Value;
15use rainier_orm::{Dialect, ExecOutcome, Row, ShardRoute};
16use rainier_support::{BoxFuture, Error, Result};
17
18use crate::connection::{Connection, Database};
19use crate::row::{ColumnRequest, OwnedRow};
20
21#[derive(Debug, Clone, PartialEq)]
23pub struct RecordedStatement {
24 pub sql: String,
26 pub params: Vec<Value>,
28 pub route: ShardRoute,
30}
31
32#[derive(Default)]
34pub struct MemoryConnection {
35 dialect: Option<Dialect>,
36 shard_family: Option<String>,
37 allocated_id: Option<u64>,
38 recorded: Mutex<Vec<RecordedStatement>>,
39 queued: Mutex<Vec<Vec<OwnedRow>>>,
40 outcome: Mutex<ExecOutcome>,
41 failure: Option<String>,
42}
43
44impl MemoryConnection {
45 pub fn new(dialect: Dialect) -> Self {
48 Self {
49 dialect: Some(dialect),
50 outcome: Mutex::new(ExecOutcome { rows_affected: 1, last_insert_id: 1 }),
51 ..Self::default()
52 }
53 }
54
55 pub fn returning(self, rows: impl IntoIterator<Item = OwnedRow>) -> Self {
60 self.queued.lock().expect("queue lock poisoned").push(rows.into_iter().collect());
61 self
62 }
63
64 pub fn with_outcome(self, rows_affected: u64, last_insert_id: i64) -> Self {
66 *self.outcome.lock().expect("outcome lock poisoned") =
67 ExecOutcome { rows_affected, last_insert_id };
68 self
69 }
70
71 pub fn sharded(mut self, family: impl Into<String>) -> Self {
73 self.shard_family = Some(family.into());
74 self
75 }
76
77 pub fn allocating(mut self, id: u64) -> Self {
79 self.allocated_id = Some(id);
80 self
81 }
82
83 pub fn failing(mut self, message: impl Into<String>) -> Self {
85 self.failure = Some(message.into());
86 self
87 }
88
89 pub fn recorded(&self) -> Vec<RecordedStatement> {
91 self.recorded.lock().expect("recorded lock poisoned").clone()
92 }
93
94 pub fn statements(&self) -> Vec<String> {
96 self.recorded().into_iter().map(|r| r.sql).collect()
97 }
98
99 pub fn last_statement(&self) -> Option<String> {
101 self.recorded().last().map(|r| r.sql.clone())
102 }
103
104 pub fn last_route(&self) -> Option<ShardRoute> {
106 self.recorded().last().map(|r| r.route)
107 }
108
109 pub fn bindings(&self) -> Vec<Vec<Value>> {
111 self.recorded().into_iter().map(|r| r.params).collect()
112 }
113
114 pub fn statement_count(&self) -> usize {
116 self.recorded.lock().expect("recorded lock poisoned").len()
117 }
118
119 fn record(&self, route: ShardRoute, sql: &str, params: Vec<Value>) -> Result<()> {
120 self.recorded.lock().expect("recorded lock poisoned").push(RecordedStatement {
121 sql: sql.to_string(),
122 params,
123 route,
124 });
125 match &self.failure {
126 Some(message) => Err(Error::internal(message.clone())),
127 None => Ok(()),
128 }
129 }
130
131 fn take_rows(&self) -> Vec<OwnedRow> {
132 let mut queued = self.queued.lock().expect("queue lock poisoned");
133 if queued.is_empty() {
134 return Vec::new();
135 }
136 queued.remove(0)
137 }
138}
139
140impl std::fmt::Debug for MemoryConnection {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 f.debug_struct("MemoryConnection")
143 .field("dialect", &self.dialect)
144 .field("statements", &self.statement_count())
145 .finish()
146 }
147}
148
149impl Connection for MemoryConnection {
150 fn dialect(&self) -> Dialect {
151 self.dialect.unwrap_or(Dialect::Sqlite)
152 }
153
154 fn shard_family(&self) -> Option<String> {
155 self.shard_family.clone()
156 }
157
158 fn allocate_id(&self, _shard_key: u64) -> Option<u64> {
159 self.allocated_id
160 }
161
162 fn fetch<'a>(
163 &'a self,
164 route: ShardRoute,
165 sql: &'a str,
166 params: Vec<Value>,
167 _columns: Vec<ColumnRequest>,
168 ) -> BoxFuture<'a, Result<Vec<OwnedRow>>> {
169 Box::pin(async move {
170 self.record(route, sql, params)?;
171 Ok(self.take_rows())
174 })
175 }
176
177 fn execute<'a>(
178 &'a self,
179 route: ShardRoute,
180 sql: &'a str,
181 params: Vec<Value>,
182 ) -> BoxFuture<'a, Result<Vec<ExecOutcome>>> {
183 Box::pin(async move {
184 self.record(route, sql, params)?;
185 Ok(vec![*self.outcome.lock().expect("outcome lock poisoned")])
186 })
187 }
188
189 fn fetch_raw<'a>(
190 &'a self,
191 route: ShardRoute,
192 sql: &'a str,
193 params: Vec<Value>,
194 ) -> BoxFuture<'a, Result<Vec<Box<dyn Row>>>> {
195 Box::pin(async move {
196 self.record(route, sql, params)?;
197 Ok(self.take_rows().into_iter().map(|row| Box::new(row) as Box<dyn Row>).collect())
198 })
199 }
200}
201
202pub fn fake_database(connection: MemoryConnection) -> (Database, Arc<MemoryConnection>) {
205 let connection = Arc::new(connection);
206 (Database::from_arc(connection.clone()), connection)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::statement;
213
214 #[derive(rainier_orm::Entity, Clone, Debug, PartialEq)]
215 #[orm(table = "posts")]
216 struct Post {
217 #[orm(pk, auto_increment)]
218 id: u64,
219 title: String,
220 }
221
222 #[tokio::test]
223 async fn records_the_statement_bindings_and_route() {
224 let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
225
226 db.execute(statement::select_by_pk::<Post>(Dialect::Sqlite, 7_i64.into())).await.unwrap();
227
228 let recorded = connection.recorded();
229 assert_eq!(recorded.len(), 1);
230 assert!(recorded[0].sql.contains("posts"));
231 assert_eq!(recorded[0].params.len(), 2);
233 assert_eq!(recorded[0].params[0], 7_i64.into());
234 assert_eq!(recorded[0].route, ShardRoute::Global);
235 }
236
237 #[tokio::test]
238 async fn queued_rows_are_served_once_each() {
239 let connection = MemoryConnection::new(Dialect::Sqlite)
240 .returning([OwnedRow::new().with("id", 1_u64).with("title", "a")])
241 .returning([
242 OwnedRow::new().with("id", 2_u64).with("title", "b"),
243 OwnedRow::new().with("id", 3_u64).with("title", "c"),
244 ]);
245 let (db, _) = fake_database(connection);
246
247 let first: Vec<Post> =
248 db.fetch_all(statement::select_all::<Post>(Dialect::Sqlite)).await.unwrap();
249 assert_eq!(first.len(), 1);
250
251 let second: Vec<Post> =
252 db.fetch_all(statement::select_all::<Post>(Dialect::Sqlite)).await.unwrap();
253 assert_eq!(second.len(), 2);
254
255 let third: Vec<Post> =
256 db.fetch_all(statement::select_all::<Post>(Dialect::Sqlite)).await.unwrap();
257 assert!(third.is_empty(), "the queue is exhausted");
258 }
259
260 #[tokio::test]
261 async fn write_outcomes_are_configurable() {
262 let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite).with_outcome(5, 42));
263 let outcome = db.statement("UPDATE posts SET title = 'x'").await.unwrap();
264
265 assert_eq!(outcome.rows_affected, 5);
266 assert_eq!(outcome.last_insert_id, 42);
267 }
268
269 #[tokio::test]
270 async fn a_failing_connection_still_records_the_attempt() {
271 let (db, connection) =
272 fake_database(MemoryConnection::new(Dialect::Sqlite).failing("boom"));
273
274 assert!(db.statement("SELECT 1").await.is_err());
275 assert_eq!(connection.statement_count(), 1);
276 }
277
278 #[test]
279 fn a_sharded_fake_reports_its_family_and_mints_ids() {
280 let connection = MemoryConnection::new(Dialect::Sqlite).sharded("users").allocating(4242);
281 assert_eq!(connection.shard_family().as_deref(), Some("users"));
282 assert_eq!(connection.allocate_id(1), Some(4242));
283
284 let (db, _) = fake_database(connection);
285 assert!(db.is_sharded());
286 }
287}