1use std::marker::PhantomData;
38
39use super::backend::Backend;
40use super::pool::DbPool;
41use super::query::QueryBuilder;
42use super::timestamp::Timestamp;
43use super::uuid::Uuid;
44use super::{DbError, Model, ToColumn, Value};
45
46pub trait Repository<T: Model, ID> {
50 async fn find_by_id(&self, id: ID) -> Result<Option<T>, DbError>;
51 async fn find_all(&self) -> Result<Vec<T>, DbError>;
52 async fn save(&self, entity: &T) -> Result<T, DbError>;
53 async fn save_all(&self, entities: &[T]) -> Result<Vec<T>, DbError>;
54 async fn delete_by_id(&self, id: ID) -> Result<(), DbError>;
55 async fn delete_all_by_id(&self, ids: &[ID]) -> Result<(), DbError>;
56 async fn count(&self) -> Result<i64, DbError>;
57 async fn exists_by_id(&self, id: ID) -> Result<bool, DbError>;
58}
59
60mod sealed {
63 pub trait Sealed {}
64 impl Sealed for i64 {}
65 impl Sealed for i32 {}
66 impl Sealed for String {}
67 impl Sealed for super::Uuid {}
68}
69
70pub trait PrimaryKey: ToColumn + Send + Sync + sealed::Sealed {}
74impl PrimaryKey for i64 {}
75impl PrimaryKey for i32 {}
76impl PrimaryKey for String {}
77impl PrimaryKey for Uuid {}
78
79pub struct ModelRepository<'a, T: Model, ID> {
83 #[doc(hidden)]
84 pub pool: &'a DbPool,
85 _phantom: PhantomData<(T, ID)>,
86}
87
88impl<'a, T: Model, ID> ModelRepository<'a, T, ID> {
89 pub fn new(pool: &'a DbPool) -> Self {
90 ModelRepository { pool, _phantom: PhantomData }
91 }
92}
93
94impl<'a, T: Model, ID: PrimaryKey> ModelRepository<'a, T, ID> {
95 pub async fn insert(&self, entity: &T) -> Result<T, DbError> {
100 insert_entity(self.pool, entity).await
101 }
102
103 pub async fn find_by_id_with_deleted(&self, id: ID) -> Result<Option<T>, DbError> {
105 select_by_pk(self.pool, id.to_column(), false).await
106 }
107
108 pub async fn restore(&self, id: ID) -> Result<bool, DbError> {
112 let column = soft_delete_column_or_err::<T>("restore")?;
113 let backend = self.pool.backend();
114 let sql = format!(
115 "UPDATE {} SET {column} = NULL WHERE {} = {} AND {column} IS NOT NULL",
116 T::table_name(),
117 T::primary_key_name(),
118 placeholder(backend, 1),
119 );
120 Ok(self.pool.execute(&sql, &[id.to_column()]).await? > 0)
121 }
122
123 pub async fn force_delete_by_id(&self, id: ID) -> Result<(), DbError> {
125 let sql = format!("DELETE FROM {} WHERE {} = {}", T::table_name(), T::primary_key_name(), placeholder(self.pool.backend(), 1));
126 self.pool.execute(&sql, &[id.to_column()]).await?;
127 Ok(())
128 }
129
130 pub fn query(&self) -> QueryBuilder<'a, T> {
132 QueryBuilder::new(self.pool)
133 }
134}
135
136macro_rules! impl_repository {
139 ($($id:ty),*) => {$(
140 impl<'a, T: Model> Repository<T, $id> for ModelRepository<'a, T, $id> {
141 async fn find_by_id(&self, id: $id) -> Result<Option<T>, DbError> {
142 select_by_pk(self.pool, id.to_column(), true).await
143 }
144
145 async fn find_all(&self) -> Result<Vec<T>, DbError> {
146 let sql = format!("SELECT * FROM {}{}", T::table_name(), live_where::<T>());
147 let rows = self.pool.query_rows(&sql, &[]).await?;
148 rows.iter().map(|r| T::from_row(r)).collect()
149 }
150
151 async fn save(&self, entity: &T) -> Result<T, DbError> {
152 save_entity(self.pool, entity).await
153 }
154
155 async fn save_all(&self, entities: &[T]) -> Result<Vec<T>, DbError> {
156 let mut result = Vec::with_capacity(entities.len());
157 for e in entities {
158 result.push(save_entity(self.pool, e).await?);
159 }
160 Ok(result)
161 }
162
163 async fn delete_by_id(&self, id: $id) -> Result<(), DbError> {
164 delete_entity::<T>(self.pool, id.to_column()).await
165 }
166
167 async fn delete_all_by_id(&self, ids: &[$id]) -> Result<(), DbError> {
168 for id in ids {
169 delete_entity::<T>(self.pool, id.to_column()).await?;
170 }
171 Ok(())
172 }
173
174 async fn count(&self) -> Result<i64, DbError> {
175 let sql = format!("SELECT COUNT(*) FROM {}{}", T::table_name(), live_where::<T>());
176 let rows = self.pool.query_rows(&sql, &[]).await?;
177 extract_count(rows)
178 }
179
180 async fn exists_by_id(&self, id: $id) -> Result<bool, DbError> {
181 Ok(select_by_pk::<T>(self.pool, id.to_column(), true).await?.is_some())
182 }
183 }
184 )*};
185}
186
187impl_repository!(i64, i32, String, Uuid);
188
189#[doc(hidden)]
194pub fn placeholder(backend: Backend, _pos: usize) -> String {
195 match backend {
196 #[cfg(feature = "model-sqlite")]
197 Backend::Sqlite => "?".to_string(),
198 #[cfg(feature = "model-postgres")]
199 Backend::Postgres => format!("${}", _pos),
200 #[cfg(feature = "model-mysql")]
201 Backend::MySql => "?".to_string(),
202 }
203}
204
205#[doc(hidden)]
207pub fn placeholders(backend: Backend, count: usize) -> String {
208 (1..=count).map(|i| placeholder(backend, i)).collect::<Vec<_>>().join(", ")
209}
210
211#[doc(hidden)]
215pub fn timestamp_placeholder(backend: Backend, pos: usize) -> String {
216 match backend {
217 #[cfg(feature = "model-postgres")]
218 Backend::Postgres => format!("CAST(${} AS TIMESTAMPTZ)", pos),
219 #[allow(unreachable_patterns)]
220 _ => placeholder(backend, pos),
221 }
222}
223
224#[doc(hidden)]
226pub fn live_condition<T: Model>() -> Option<String> {
227 T::soft_delete_column().map(|c| format!("{c} IS NULL"))
228}
229
230fn live_where<T: Model>() -> String {
231 live_condition::<T>().map(|c| format!(" WHERE {c}")).unwrap_or_default()
232}
233
234fn soft_delete_column_or_err<T: Model>(what: &str) -> Result<&'static str, DbError> {
235 T::soft_delete_column().ok_or_else(|| {
236 DbError::new(format!("{what}: {} has no soft-delete column (add #[soft_delete] and a deleted_at field)", T::table_name()))
237 })
238}
239
240fn is_timestamp_column<T: Model>(col: &str) -> bool {
241 [T::created_at_column(), T::updated_at_column(), T::soft_delete_column()].contains(&Some(col))
242}
243
244fn is_nil_uuid(v: &Value) -> bool {
245 matches!(v, Value::Text(s) if s == "00000000-0000-0000-0000-000000000000")
246}
247
248#[doc(hidden)]
252pub async fn select_by_pk<T: Model>(pool: &DbPool, pk: Value, live_only: bool) -> Result<Option<T>, DbError> {
253 let mut sql = format!("SELECT * FROM {} WHERE {} = {}", T::table_name(), T::primary_key_name(), placeholder(pool.backend(), 1));
254 if live_only {
255 if let Some(c) = live_condition::<T>() {
256 sql.push_str(" AND ");
257 sql.push_str(&c);
258 }
259 }
260 let rows = pool.query_rows(&sql, &[pk]).await?;
261 match rows.into_iter().next() {
262 Some(row) => Ok(Some(T::from_row(&row)?)),
263 None => Ok(None),
264 }
265}
266
267#[doc(hidden)]
269pub async fn save_entity<T: Model>(pool: &DbPool, entity: &T) -> Result<T, DbError> {
270 let pool = pool.primary();
273 let pk = entity.primary_key_value();
274 if T::primary_key_auto_increment() {
275 return match pk {
276 Value::Null | Value::Int(0) => insert_entity(pool, entity).await,
277 _ => update_entity(pool, entity).await,
278 };
279 }
280 match &pk {
281 Value::Null => Err(DbError::new(format!(
282 "save: {}.{} is NULL — set the key, or mark it #[primary_key(auto_increment)]",
283 T::table_name(),
284 T::primary_key_name()
285 ))),
286 Value::Text(s) if s.is_empty() => Err(DbError::new(format!(
287 "save: {}.{} is empty — set the key before saving",
288 T::table_name(),
289 T::primary_key_name()
290 ))),
291 key if is_nil_uuid(key) => insert_entity(pool, entity).await,
292 _ => {
293 let sql = format!("SELECT 1 FROM {} WHERE {} = {}", T::table_name(), T::primary_key_name(), placeholder(pool.backend(), 1));
294 if pool.query_rows(&sql, &[pk]).await?.is_empty() {
295 insert_entity(pool, entity).await
296 } else {
297 update_entity(pool, entity).await
298 }
299 }
300 }
301}
302
303#[doc(hidden)]
304pub async fn insert_entity<T: Model>(pool: &DbPool, entity: &T) -> Result<T, DbError> {
305 let pool = pool.primary();
306 let backend = pool.backend();
307 let now = Timestamp::now().to_value(backend);
308 let pk_name = T::primary_key_name();
309 let mut key = entity.primary_key_value();
310
311 let mut cols: Vec<&str> = Vec::new();
312 let mut params: Vec<Value> = Vec::new();
313 let mut marks: Vec<String> = Vec::new();
314 for (col, value) in entity.to_values() {
315 let value = if col == pk_name {
316 if T::primary_key_auto_increment() {
317 continue;
318 }
319 if is_nil_uuid(&value) {
320 key = Value::Text(Uuid::now_v7().to_string());
321 }
322 key.clone()
323 } else if Some(col) == T::created_at_column() || Some(col) == T::updated_at_column() {
324 now.clone()
325 } else if Some(col) == T::soft_delete_column() {
326 Value::Null
327 } else if Some(col) == T::version_column() {
328 Value::Int(1)
329 } else {
330 value
331 };
332 cols.push(col);
333 if value == Value::Null {
334 marks.push("NULL".to_string());
337 continue;
338 }
339 params.push(value);
340 let pos = params.len();
341 marks.push(if is_timestamp_column::<T>(col) { timestamp_placeholder(backend, pos) } else { placeholder(backend, pos) });
342 }
343
344 let insert_sql = if cols.is_empty() {
345 match backend {
346 #[cfg(feature = "model-mysql")]
347 Backend::MySql => format!("INSERT INTO {} () VALUES ()", T::table_name()),
348 #[allow(unreachable_patterns)]
349 _ => format!("INSERT INTO {} DEFAULT VALUES", T::table_name()),
350 }
351 } else {
352 format!("INSERT INTO {} ({}) VALUES ({})", T::table_name(), cols.join(", "), marks.join(", "))
353 };
354
355 if T::primary_key_auto_increment() {
356 key = Value::Int(pool.insert_returning_id(&insert_sql, ¶ms, pk_name).await?);
357 } else {
358 pool.execute(&insert_sql, ¶ms).await?;
359 }
360 select_by_pk(pool, key, false).await?.ok_or_else(|| DbError::new("inserted row not found"))
361}
362
363#[doc(hidden)]
364pub async fn update_entity<T: Model>(pool: &DbPool, entity: &T) -> Result<T, DbError> {
365 let pool = pool.primary();
366 let backend = pool.backend();
367 let pk = entity.primary_key_value();
368 let pk_name = T::primary_key_name();
369 let mut sets: Vec<String> = Vec::new();
370 let mut params: Vec<Value> = Vec::new();
371 let mut expected_version: Option<Value> = None;
372 for (col, value) in entity.to_values() {
373 if col == pk_name || Some(col) == T::created_at_column() || Some(col) == T::updated_at_column() || Some(col) == T::soft_delete_column() {
374 continue;
375 }
376 if Some(col) == T::version_column() {
377 expected_version = Some(value);
378 continue;
379 }
380 if value == Value::Null {
381 sets.push(format!("{col} = NULL"));
382 continue;
383 }
384 params.push(value);
385 sets.push(format!("{} = {}", col, placeholder(backend, params.len())));
386 }
387 if let Some(col) = T::updated_at_column() {
388 params.push(Timestamp::now().to_value(backend));
389 sets.push(format!("{} = {}", col, timestamp_placeholder(backend, params.len())));
390 }
391 if let Some(col) = T::version_column() {
392 sets.push(format!("{col} = {col} + 1"));
393 }
394 if sets.is_empty() {
395 return select_by_pk(pool, pk.clone(), false)
397 .await?
398 .ok_or_else(|| DbError::not_found(format!("{} {}={}: no such row to update", T::table_name(), pk_name, show(&pk))));
399 }
400
401 params.push(pk.clone());
402 let mut sql = format!(
403 "UPDATE {} SET {} WHERE {} = {}",
404 T::table_name(),
405 sets.join(", "),
406 pk_name,
407 placeholder(backend, params.len()),
408 );
409 if let (Some(col), Some(version)) = (T::version_column(), &expected_version) {
410 params.push(version.clone());
411 sql.push_str(&format!(" AND {} = {}", col, placeholder(backend, params.len())));
412 }
413
414 if pool.execute(&sql, ¶ms).await? == 0 {
415 let current = select_by_pk::<T>(pool, pk.clone(), false).await?;
416 let (Some(row), Some(col), Some(expected)) = (current, T::version_column(), expected_version) else {
417 return Err(DbError::not_found(format!("{} {}={}: no such row to update", T::table_name(), pk_name, show(&pk))));
418 };
419 let now_at = row.to_values().into_iter().find(|(c, _)| *c == col).map(|(_, v)| v).unwrap_or(Value::Null);
420 return Err(DbError::stale_version(format!(
421 "{} {}={}: the row is at {} {}, this copy has {} — someone saved it since it was read; reload it and apply the change again",
422 T::table_name(),
423 pk_name,
424 show(&pk),
425 col,
426 show(&now_at),
427 show(&expected),
428 )));
429 }
430
431 select_by_pk(pool, pk, false).await?.ok_or_else(|| DbError::new("updated row not found"))
432}
433
434#[doc(hidden)]
437pub async fn delete_entity<T: Model>(pool: &DbPool, pk: Value) -> Result<(), DbError> {
438 let backend = pool.backend();
439 match T::soft_delete_column() {
440 Some(col) => {
441 let sql = format!(
442 "UPDATE {} SET {col} = {} WHERE {} = {} AND {col} IS NULL",
443 T::table_name(),
444 timestamp_placeholder(backend, 1),
445 T::primary_key_name(),
446 placeholder(backend, 2),
447 );
448 pool.execute(&sql, &[Timestamp::now().to_value(backend), pk]).await?;
449 }
450 None => {
451 let sql = format!("DELETE FROM {} WHERE {} = {}", T::table_name(), T::primary_key_name(), placeholder(backend, 1));
452 pool.execute(&sql, &[pk]).await?;
453 }
454 }
455 Ok(())
456}
457
458fn show(v: &Value) -> String {
459 match v {
460 Value::Null => "NULL".to_string(),
461 Value::Bool(b) => b.to_string(),
462 Value::Int(n) => n.to_string(),
463 Value::Float(f) => f.to_string(),
464 Value::Text(s) => format!("{s:?}"),
465 Value::Bytes(b) => format!("<{} bytes>", b.len()),
466 }
467}
468
469#[doc(hidden)]
470pub fn extract_count(rows: Vec<super::ModelRow>) -> Result<i64, DbError> {
471 match rows.into_iter().next() {
472 Some(row) => {
473 if let Some((_, val)) = row.columns.first() {
474 match val.clone() {
475 Value::Int(n) => return Ok(n),
476 other => return Err(DbError::new(format!("unexpected count value: {:?}", other))),
477 }
478 }
479 Err(DbError::new("count query returned empty row"))
480 }
481 None => Ok(0),
482 }
483}