1use std::collections::HashSet;
11use std::marker::PhantomData;
12
13use super::eager::{distinct_keys, key_of, IN_BATCH};
14use super::pool::{DbPool, DbTransaction};
15use super::repository::{live_condition, placeholder};
16use super::{DbError, Model, ToColumn, Value};
17
18#[derive(Debug, Clone)]
29pub struct HasMany<T: Model> {
30 owner_pk: Value,
31 fk_col: &'static str,
32 _phantom: PhantomData<T>,
33}
34
35impl<T: Model> HasMany<T> {
36 pub fn new(owner_pk: Value, fk_col: &'static str) -> Self {
37 HasMany { owner_pk, fk_col, _phantom: PhantomData }
38 }
39
40 pub async fn load(&self, pool: &DbPool) -> Result<Vec<T>, DbError> {
42 let sql = format!(
43 "SELECT * FROM {} WHERE {} = {}{}",
44 T::table_name(), self.fk_col, placeholder(pool.backend(), 1), and_live::<T>(),
45 );
46 let rows = pool.query_rows(&sql, &[self.owner_pk.clone()]).await?;
47 rows.iter().map(|r| T::from_row(r)).collect()
48 }
49}
50
51#[derive(Debug, Clone)]
57pub struct HasOne<O: Model> {
58 owner_pk: Value,
59 fk_col: &'static str,
60 _phantom: PhantomData<O>,
61}
62
63impl<O: Model> HasOne<O> {
64 pub fn new(owner_pk: Value, fk_col: &'static str) -> Self {
65 HasOne { owner_pk, fk_col, _phantom: PhantomData }
66 }
67
68 pub async fn load(&self, pool: &DbPool) -> Result<Option<O>, DbError> {
70 let sql = format!(
71 "SELECT * FROM {} WHERE {} = {}{} LIMIT 1",
72 O::table_name(), self.fk_col, placeholder(pool.backend(), 1), and_live::<O>(),
73 );
74 let rows = pool.query_rows(&sql, &[self.owner_pk.clone()]).await?;
75 match rows.into_iter().next() {
76 Some(row) => Ok(Some(O::from_row(&row)?)),
77 None => Ok(None),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
88pub struct BelongsTo<O: Model> {
89 fk_value: Value,
90 _phantom: PhantomData<O>,
91}
92
93impl<O: Model> BelongsTo<O> {
94 pub fn new(fk_value: Value) -> Self {
95 BelongsTo { fk_value, _phantom: PhantomData }
96 }
97
98 pub async fn load(&self, pool: &DbPool) -> Result<Option<O>, DbError> {
100 let sql = format!(
101 "SELECT * FROM {} WHERE {} = {}{} LIMIT 1",
102 O::table_name(), O::primary_key_name(), placeholder(pool.backend(), 1), and_live::<O>(),
103 );
104 let rows = pool.query_rows(&sql, &[self.fk_value.clone()]).await?;
105 match rows.into_iter().next() {
106 Some(row) => Ok(Some(O::from_row(&row)?)),
107 None => Ok(None),
108 }
109 }
110}
111
112fn and_live<T: Model>() -> String {
113 live_condition::<T>().map(|c| format!(" AND {c}")).unwrap_or_default()
114}
115
116pub struct BelongsToMany<R: Model> {
140 owner_pk: Value,
141 join_table: &'static str,
142 owner_key: &'static str,
143 related_key: &'static str,
144 _phantom: PhantomData<fn() -> R>,
145}
146
147impl<R: Model> Clone for BelongsToMany<R> {
148 fn clone(&self) -> Self {
149 BelongsToMany { owner_pk: self.owner_pk.clone(), ..*self }
150 }
151}
152
153impl<R: Model> std::fmt::Debug for BelongsToMany<R> {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 f.debug_struct("BelongsToMany")
156 .field("owner_pk", &self.owner_pk)
157 .field("join_table", &self.join_table)
158 .field("owner_key", &self.owner_key)
159 .field("related_key", &self.related_key)
160 .finish()
161 }
162}
163
164#[derive(Debug, Clone, Default, PartialEq)]
167#[non_exhaustive]
168pub struct SyncChanges {
169 pub attached: Vec<Value>,
170 pub detached: Vec<Value>,
171}
172
173fn keys<I>(ids: I) -> Vec<Value>
175where
176 I: IntoIterator,
177 I::Item: ToColumn,
178{
179 distinct_keys(&ids.into_iter().map(|id| id.to_column()).collect::<Vec<_>>())
180}
181
182fn first_column(row: super::ModelRow) -> Option<Value> {
183 row.columns.into_iter().next().map(|(_, v)| v)
184}
185
186impl<R: Model> BelongsToMany<R> {
187 pub fn new(owner_pk: Value, join_table: &'static str, owner_key: &'static str, related_key: &'static str) -> Self {
189 BelongsToMany { owner_pk, join_table, owner_key, related_key, _phantom: PhantomData }
190 }
191
192 pub async fn load(&self, pool: &DbPool) -> Result<Vec<R>, DbError> {
194 let pk = R::primary_key_name();
195 let live = R::soft_delete_column().map(|c| format!(" AND r.{c} IS NULL")).unwrap_or_default();
196 let sql = format!(
197 "SELECT r.* FROM {} r INNER JOIN {} j ON j.{} = r.{pk} WHERE j.{} = {}{live} ORDER BY r.{pk}",
198 R::table_name(),
199 self.join_table,
200 self.related_key,
201 self.owner_key,
202 placeholder(pool.backend(), 1),
203 );
204 let rows = pool.query_rows(&sql, &[self.owner_pk.clone()]).await?;
205 rows.iter().map(|r| R::from_row(r)).collect()
206 }
207
208 pub async fn ids(&self, pool: &DbPool) -> Result<Vec<Value>, DbError> {
210 let rows = pool.query_rows(&self.ids_sql(pool), &[self.owner_pk.clone()]).await?;
211 Ok(rows.into_iter().filter_map(first_column).collect())
212 }
213
214 fn ids_sql(&self, pool: &DbPool) -> String {
215 format!("SELECT {} FROM {} WHERE {} = {}", self.related_key, self.join_table, self.owner_key, placeholder(pool.backend(), 1))
216 }
217
218 pub async fn attach<I>(&self, pool: &DbPool, ids: I) -> Result<u64, DbError>
221 where
222 I: IntoIterator,
223 I::Item: ToColumn,
224 {
225 let wanted = keys(ids);
226 if wanted.is_empty() {
227 return Ok(0);
228 }
229 let mut tx = pool.begin().await?;
230 let existing = self.linked(&mut tx, pool).await?.1;
231 let mut added = 0;
232 for id in wanted {
233 if key_of(id.clone()).is_some_and(|k| !existing.contains(&k)) {
234 self.insert_link(&mut tx, pool, id).await?;
235 added += 1;
236 }
237 }
238 tx.commit().await?;
239 Ok(added)
240 }
241
242 pub async fn detach<I>(&self, pool: &DbPool, ids: I) -> Result<u64, DbError>
245 where
246 I: IntoIterator,
247 I::Item: ToColumn,
248 {
249 let backend = pool.backend();
250 let mut removed = 0;
251 for batch in keys(ids).chunks(IN_BATCH) {
252 let list = (2..=batch.len() + 1).map(|i| placeholder(backend, i)).collect::<Vec<_>>().join(", ");
253 let sql = format!(
254 "DELETE FROM {} WHERE {} = {} AND {} IN ({list})",
255 self.join_table,
256 self.owner_key,
257 placeholder(backend, 1),
258 self.related_key
259 );
260 let mut params = vec![self.owner_pk.clone()];
261 params.extend(batch.iter().cloned());
262 removed += pool.execute(&sql, ¶ms).await?;
263 }
264 Ok(removed)
265 }
266
267 pub async fn detach_all(&self, pool: &DbPool) -> Result<u64, DbError> {
270 let sql = format!("DELETE FROM {} WHERE {} = {}", self.join_table, self.owner_key, placeholder(pool.backend(), 1));
271 pool.execute(&sql, &[self.owner_pk.clone()]).await
272 }
273
274 pub async fn sync<I>(&self, pool: &DbPool, ids: I) -> Result<SyncChanges, DbError>
277 where
278 I: IntoIterator,
279 I::Item: ToColumn,
280 {
281 let wanted = keys(ids);
282 let wanted_keys: HashSet<String> = wanted.iter().filter_map(|v| key_of(v.clone())).collect();
283 let backend = pool.backend();
284 let mut tx = pool.begin().await?;
285 let (existing, existing_keys) = self.linked(&mut tx, pool).await?;
286 let mut changes = SyncChanges::default();
287 for id in existing {
288 if key_of(id.clone()).is_some_and(|k| !wanted_keys.contains(&k)) {
289 let sql = format!(
290 "DELETE FROM {} WHERE {} = {} AND {} = {}",
291 self.join_table,
292 self.owner_key,
293 placeholder(backend, 1),
294 self.related_key,
295 placeholder(backend, 2)
296 );
297 tx.execute(&sql, &[self.owner_pk.clone(), id.clone()]).await?;
298 changes.detached.push(id);
299 }
300 }
301 for id in wanted {
302 if key_of(id.clone()).is_some_and(|k| !existing_keys.contains(&k)) {
303 self.insert_link(&mut tx, pool, id.clone()).await?;
304 changes.attached.push(id);
305 }
306 }
307 tx.commit().await?;
308 Ok(changes)
309 }
310
311 async fn linked(&self, tx: &mut DbTransaction, pool: &DbPool) -> Result<(Vec<Value>, HashSet<String>), DbError> {
313 let rows = tx.query_rows(&self.ids_sql(pool), &[self.owner_pk.clone()]).await?;
314 let values: Vec<Value> = rows.into_iter().filter_map(first_column).collect();
315 let keys = values.iter().filter_map(|v| key_of(v.clone())).collect();
316 Ok((values, keys))
317 }
318
319 async fn insert_link(&self, tx: &mut DbTransaction, pool: &DbPool, id: Value) -> Result<(), DbError> {
320 let backend = pool.backend();
321 let sql = format!(
322 "INSERT INTO {} ({}, {}) VALUES ({}, {})",
323 self.join_table,
324 self.owner_key,
325 self.related_key,
326 placeholder(backend, 1),
327 placeholder(backend, 2)
328 );
329 tx.execute(&sql, &[self.owner_pk.clone(), id]).await?;
330 Ok(())
331 }
332}