Skip to main content

rws_server/model/
repository.rs

1//! Async `Repository` trait and `ModelRepository` implementation.
2
3use std::marker::PhantomData;
4
5use super::backend::Backend;
6use super::pool::DbPool;
7use super::{DbError, Model, Value};
8
9// ── Repository trait ──────────────────────────────────────────────────────────
10
11/// Async CRUD operations for a model type.
12pub trait Repository<T: Model, ID> {
13    async fn find_by_id(&self, id: ID) -> Result<Option<T>, DbError>;
14    async fn find_all(&self) -> Result<Vec<T>, DbError>;
15    async fn save(&self, entity: &T) -> Result<T, DbError>;
16    async fn save_all(&self, entities: &[T]) -> Result<Vec<T>, DbError>;
17    async fn delete_by_id(&self, id: ID) -> Result<(), DbError>;
18    async fn delete_all_by_id(&self, ids: &[ID]) -> Result<(), DbError>;
19    async fn count(&self) -> Result<i64, DbError>;
20    async fn exists_by_id(&self, id: ID) -> Result<bool, DbError>;
21}
22
23// ── ModelRepository ───────────────────────────────────────────────────────────
24
25/// Repository tied to a model type and a pool.
26pub struct ModelRepository<'a, T: Model, ID> {
27    #[doc(hidden)]
28    pub pool: &'a DbPool,
29    _phantom: PhantomData<(T, ID)>,
30}
31
32impl<'a, T: Model, ID> ModelRepository<'a, T, ID> {
33    pub fn new(pool: &'a DbPool) -> Self {
34        ModelRepository { pool, _phantom: PhantomData }
35    }
36}
37
38// ── impl Repository<T, i64> ───────────────────────────────────────────────────
39
40impl<'a, T: Model> Repository<T, i64> for ModelRepository<'a, T, i64> {
41    async fn find_by_id(&self, id: i64) -> Result<Option<T>, DbError> {
42        let sql = format!(
43            "SELECT * FROM {} WHERE {} = {}",
44            T::table_name(),
45            T::primary_key_name(),
46            placeholder(self.pool.backend(), 1),
47        );
48        let rows = self.pool.query_rows(&sql, &[Value::Int(id)]).await?;
49        match rows.into_iter().next() {
50            Some(row) => Ok(Some(T::from_row(&row)?)),
51            None => Ok(None),
52        }
53    }
54
55    async fn find_all(&self) -> Result<Vec<T>, DbError> {
56        let sql = format!("SELECT * FROM {}", T::table_name());
57        let rows = self.pool.query_rows(&sql, &[]).await?;
58        rows.iter().map(|r| T::from_row(r)).collect()
59    }
60
61    async fn save(&self, entity: &T) -> Result<T, DbError> {
62        let pk_val = entity.primary_key_value();
63        let is_new = matches!(&pk_val, Value::Int(n) if *n == 0) || matches!(&pk_val, Value::Null);
64        if is_new {
65            insert_entity(self.pool, entity).await
66        } else {
67            update_entity(self.pool, entity).await
68        }
69    }
70
71    async fn save_all(&self, entities: &[T]) -> Result<Vec<T>, DbError> {
72        let mut result = Vec::with_capacity(entities.len());
73        for e in entities {
74            result.push(self.save(e).await?);
75        }
76        Ok(result)
77    }
78
79    async fn delete_by_id(&self, id: i64) -> Result<(), DbError> {
80        let sql = format!(
81            "DELETE FROM {} WHERE {} = {}",
82            T::table_name(),
83            T::primary_key_name(),
84            placeholder(self.pool.backend(), 1),
85        );
86        self.pool.execute(&sql, &[Value::Int(id)]).await?;
87        Ok(())
88    }
89
90    async fn delete_all_by_id(&self, ids: &[i64]) -> Result<(), DbError> {
91        for &id in ids {
92            self.delete_by_id(id).await?;
93        }
94        Ok(())
95    }
96
97    async fn count(&self) -> Result<i64, DbError> {
98        let sql = format!("SELECT COUNT(*) FROM {}", T::table_name());
99        let rows = self.pool.query_rows(&sql, &[]).await?;
100        extract_count(rows)
101    }
102
103    async fn exists_by_id(&self, id: i64) -> Result<bool, DbError> {
104        Ok(self.find_by_id(id).await?.is_some())
105    }
106}
107
108// ── Placeholder helpers ───────────────────────────────────────────────────────
109
110/// Return the DB-appropriate placeholder for position `pos` (1-indexed) on
111/// `backend`. SQLite and MySQL use `?`, PostgreSQL uses `$1`, `$2`, …
112#[doc(hidden)]
113pub fn placeholder(backend: Backend, _pos: usize) -> String {
114    match backend {
115        #[cfg(feature = "model-sqlite")]
116        Backend::Sqlite => "?".to_string(),
117        #[cfg(feature = "model-postgres")]
118        Backend::Postgres => format!("${}", _pos),
119        #[cfg(feature = "model-mysql")]
120        Backend::MySql => "?".to_string(),
121    }
122}
123
124/// Build a comma-separated list of placeholders.
125#[doc(hidden)]
126pub fn placeholders(backend: Backend, count: usize) -> String {
127    (1..=count).map(|i| placeholder(backend, i)).collect::<Vec<_>>().join(", ")
128}
129
130// ── Insert / Update helpers ───────────────────────────────────────────────────
131
132#[doc(hidden)]
133pub async fn insert_entity<T: Model>(pool: &DbPool, entity: &T) -> Result<T, DbError> {
134    let backend = pool.backend();
135    let values = entity.to_values();
136
137    let insert_vals: Vec<(&'static str, Value)> = if T::primary_key_auto_increment() {
138        values.into_iter().filter(|(col, _)| *col != T::primary_key_name()).collect()
139    } else {
140        values
141    };
142
143    let cols: Vec<&str> = insert_vals.iter().map(|(c, _)| *c).collect();
144    let params: Vec<Value> = insert_vals.into_iter().map(|(_, v)| v).collect();
145    let ph = placeholders(backend, cols.len());
146    let col_list = cols.join(", ");
147
148    let insert_sql = format!(
149        "INSERT INTO {} ({}) VALUES ({})",
150        T::table_name(), col_list, ph
151    );
152
153    let new_id = pool.insert_returning_id(&insert_sql, &params, T::primary_key_name()).await?;
154    let select_sql = format!(
155        "SELECT * FROM {} WHERE {} = {}",
156        T::table_name(), T::primary_key_name(), placeholder(backend, 1),
157    );
158    let rows = pool.query_rows(&select_sql, &[Value::Int(new_id)]).await?;
159    rows.into_iter().next()
160        .ok_or_else(|| DbError::new("inserted row not found"))
161        .and_then(|r| T::from_row(&r))
162}
163
164#[doc(hidden)]
165pub async fn update_entity<T: Model>(pool: &DbPool, entity: &T) -> Result<T, DbError> {
166    let backend = pool.backend();
167    let values = entity.to_values();
168    let set_pairs: Vec<(&str, Value)> = values
169        .into_iter()
170        .filter(|(col, _)| *col != T::primary_key_name())
171        .collect();
172
173    let set_clause: String = set_pairs
174        .iter()
175        .enumerate()
176        .map(|(i, (col, _))| format!("{} = {}", col, placeholder(backend, i + 1)))
177        .collect::<Vec<_>>()
178        .join(", ");
179
180    let pk_placeholder = placeholder(backend, set_pairs.len() + 1);
181    let sql = format!(
182        "UPDATE {} SET {} WHERE {} = {}",
183        T::table_name(), set_clause, T::primary_key_name(), pk_placeholder,
184    );
185
186    let mut params: Vec<Value> = set_pairs.into_iter().map(|(_, v)| v).collect();
187    params.push(entity.primary_key_value());
188    pool.execute(&sql, &params).await?;
189
190    let pk_id = match entity.primary_key_value() {
191        Value::Int(n) => n,
192        _ => return Err(DbError::new("primary key must be an integer for UPDATE")),
193    };
194    let select_sql = format!(
195        "SELECT * FROM {} WHERE {} = {}",
196        T::table_name(), T::primary_key_name(), placeholder(backend, 1),
197    );
198    let rows = pool.query_rows(&select_sql, &[Value::Int(pk_id)]).await?;
199    rows.into_iter().next()
200        .ok_or_else(|| DbError::new("updated row not found"))
201        .and_then(|r| T::from_row(&r))
202}
203
204#[doc(hidden)]
205pub fn extract_count(rows: Vec<super::ModelRow>) -> Result<i64, DbError> {
206    match rows.into_iter().next() {
207        Some(row) => {
208            if let Some((_, val)) = row.columns.first() {
209                match val.clone() {
210                    Value::Int(n) => return Ok(n),
211                    other => return Err(DbError::new(format!("unexpected count value: {:?}", other))),
212                }
213            }
214            Err(DbError::new("count query returned empty row"))
215        }
216        None => Ok(0),
217    }
218}