1use std::collections::HashMap;
31use std::marker::PhantomData;
32
33use super::{Db, Executor, Model, ModelKey, Query, ToDbValue, quote, sql};
34use crate::Result;
35
36pub trait ForeignKey<K>: foreign_key::Sealed<K> {
39 fn key(self) -> Option<K>;
41}
42
43mod foreign_key {
44 pub trait Sealed<K> {}
46 impl<K: super::ModelKey> Sealed<K> for K {}
47 impl<K: super::ModelKey> Sealed<K> for Option<K> {}
48}
49
50impl<K: ModelKey> ForeignKey<K> for K {
51 fn key(self) -> Option<K> {
52 Some(self)
53 }
54}
55
56impl<K: ModelKey> ForeignKey<K> for Option<K> {
57 fn key(self) -> Option<K> {
58 self
59 }
60}
61
62pub fn belongs_to<'a, P: Model, C, K: ForeignKey<P::Key>>(
71 db: &'a Db,
72 children: &[C],
73 foreign_key: impl Fn(&C) -> K,
74) -> impl Future<Output = Result<HashMap<P::Key, P>>> + Send + 'a {
75 let mut ids: Vec<P::Key> = children
76 .iter()
77 .filter_map(|c| foreign_key(c).key())
78 .collect();
79 ids.sort_unstable();
80 ids.dedup();
81 async move {
82 if ids.is_empty() {
83 return Ok(HashMap::new());
84 }
85 Ok(P::find_many(db, ids)
86 .await?
87 .into_iter()
88 .map(|parent| (parent.id(), parent))
89 .collect())
90 }
91}
92
93pub fn has_many<'a, C: Model, P: Model, K: ForeignKey<P::Key>>(
98 db: &'a Db,
99 parents: &[P],
100 children: Query<C>,
101 column: &str,
102 foreign_key: impl Fn(&C) -> K + Send + 'a,
103) -> impl Future<Output = Result<HashMap<P::Key, Vec<C>>>> + Send + 'a {
104 let ids: Vec<P::Key> = parents.iter().map(Model::id).collect();
105 let query = (!ids.is_empty()).then(|| children.where_in(column, ids));
106 async move {
107 let mut grouped: HashMap<P::Key, Vec<C>> = HashMap::new();
108 let Some(query) = query else {
109 return Ok(grouped);
110 };
111 let rows = query.get(db).await?;
112 for child in rows {
113 if let Some(parent) = foreign_key(&child).key() {
114 grouped.entry(parent).or_default().push(child);
115 }
116 }
117 Ok(grouped)
118 }
119}
120
121#[allow(clippy::too_many_arguments)]
146pub fn has_many_through<'a, C, T, P, KT, KC>(
147 db: &'a Db,
148 parents: &[P],
149 through: Query<T>,
150 through_column: &str,
151 through_key: impl Fn(&T) -> KT + Send + 'a,
152 children: Query<C>,
153 column: &str,
154 foreign_key: impl Fn(&C) -> KC + Send + 'a,
155) -> impl Future<Output = Result<HashMap<P::Key, Vec<C>>>> + Send + 'a
156where
157 C: Model,
158 T: Model,
159 P: Model,
160 KT: ForeignKey<P::Key>,
161 KC: ForeignKey<T::Key>,
162{
163 let ids: Vec<P::Key> = parents.iter().map(Model::id).collect();
164 let middle = (!ids.is_empty()).then(|| through.where_in(through_column, ids));
165 let column = column.to_owned();
166 async move {
167 let mut grouped: HashMap<P::Key, Vec<C>> = HashMap::new();
168 let Some(middle) = middle else {
169 return Ok(grouped);
170 };
171 let parent_of: HashMap<T::Key, P::Key> = middle
173 .get(db)
174 .await?
175 .iter()
176 .filter_map(|row| Some((row.id(), through_key(row).key()?)))
177 .collect();
178 if parent_of.is_empty() {
179 return Ok(grouped);
180 }
181 let middle_ids: Vec<T::Key> = parent_of.keys().cloned().collect();
182 for child in children.where_in(&column, middle_ids).get(db).await? {
183 let parent = foreign_key(&child)
184 .key()
185 .and_then(|middle| parent_of.get(&middle).cloned());
186 if let Some(parent) = parent {
187 grouped.entry(parent).or_default().push(child);
188 }
189 }
190 Ok(grouped)
191 }
192}
193
194pub fn count_many<'a, C: Model, P: Model>(
210 db: &'a Db,
211 parents: &[P],
212 children: Query<C>,
213 column: &str,
214) -> impl Future<Output = Result<HashMap<P::Key, i64>>> + Send + 'a {
215 grouped(db, parents, children, column, "COUNT(*)".to_owned())
216}
217
218pub fn sum_many<'a, T: super::Number + Default + Send + 'a, C: Model, P: Model>(
221 db: &'a Db,
222 parents: &[P],
223 children: Query<C>,
224 column: &str,
225 sum_column: &str,
226) -> impl Future<Output = Result<HashMap<P::Key, T>>> + Send + 'a {
227 let expression = if C::COLUMNS.contains(&sum_column) {
228 format!(
229 "CAST(COALESCE(SUM({}), 0) AS {})",
230 quote(sum_column),
231 T::SQL_TYPE
232 )
233 } else {
234 format!("SUM({})", quote(sum_column))
236 };
237 let checked = children.check_column(sum_column);
238 grouped(db, parents, checked, column, expression)
239}
240
241fn grouped<'a, T: crate::db::FromDb + Default + Send + 'a, C: Model, P: Model>(
242 db: &'a Db,
243 parents: &[P],
244 children: Query<C>,
245 column: &str,
246 expression: String,
247) -> impl Future<Output = Result<HashMap<P::Key, T>>> + Send + 'a {
248 let ids: Vec<P::Key> = parents.iter().map(Model::id).collect();
249 let column = column.to_owned();
250 async move {
251 let mut totals: HashMap<P::Key, T> =
252 ids.iter().map(|id| (id.clone(), T::default())).collect();
253 if ids.is_empty() {
254 return Ok(totals);
255 }
256 let rows: Vec<(P::Key, T)> = children
257 .where_in(&column, ids)
258 .group_by(&column)
259 .select_as(db, &format!("{}, {expression}", quote(&column)))
260 .await?;
261 totals.extend(rows);
262 Ok(totals)
263 }
264}
265
266pub struct Pivot<L = i64, R = i64> {
275 table: &'static str,
276 left: &'static str,
277 right: &'static str,
278 timestamps: bool,
279 keys: PhantomData<fn() -> (L, R)>,
280}
281
282impl<L, R> Clone for Pivot<L, R> {
283 fn clone(&self) -> Self {
284 *self
285 }
286}
287
288impl<L, R> Copy for Pivot<L, R> {}
289
290impl<L, R> std::fmt::Debug for Pivot<L, R> {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 f.debug_struct("Pivot")
293 .field("table", &self.table)
294 .field("left", &self.left)
295 .field("right", &self.right)
296 .field("timestamps", &self.timestamps)
297 .finish()
298 }
299}
300
301pub type PivotData<'a> = &'a [(&'a str, &'a (dyn super::ToDbValue + Sync))];
303
304impl<L, R> Pivot<L, R> {
305 pub const fn new(table: &'static str, left: &'static str, right: &'static str) -> Self {
307 Self {
308 table,
309 left,
310 right,
311 timestamps: false,
312 keys: PhantomData,
313 }
314 }
315
316 pub const fn with_timestamps(mut self) -> Self {
319 self.timestamps = true;
320 self
321 }
322
323 pub const fn inverse(self) -> Pivot<R, L> {
325 Pivot {
326 table: self.table,
327 left: self.right,
328 right: self.left,
329 timestamps: self.timestamps,
330 keys: PhantomData,
331 }
332 }
333}
334
335impl<L: ModelKey, R: ModelKey> Pivot<L, R> {
336 async fn insert_link(
339 &self,
340 conn: &mut super::Conn<'_>,
341 left: &L,
342 right: &R,
343 data: PivotData<'_>,
344 ) -> Result<bool> {
345 let mut columns = vec![quote(self.left), quote(self.right)];
346 let mut values = vec![left.to_db_value(), right.to_db_value()];
347 for (column, value) in data {
348 columns.push(quote(column));
349 values.push(value.to_db_value());
350 }
351 if self.timestamps {
352 let at = super::ToDbValue::to_db_value(&super::now());
353 columns.extend([quote("created_at"), quote("updated_at")]);
354 values.extend([at.clone(), at]);
355 }
356 let marks = vec!["?"; columns.len()].join(", ");
357 let added = sql(format!(
358 "INSERT INTO {table} ({columns}) SELECT {marks} \
359 WHERE NOT EXISTS (SELECT 1 FROM {table} WHERE {l} = ? AND {r} = ?)",
360 table = quote(self.table),
361 columns = columns.join(", "),
362 l = quote(self.left),
363 r = quote(self.right),
364 ))
365 .bind_all(values)
366 .bind(left.to_db_value())
367 .bind(right.to_db_value())
368 .execute(conn.reborrow())
369 .await?;
370 Ok(added > 0)
371 }
372
373 pub async fn ids<'c>(&self, db: impl Executor<'c>, left: L) -> Result<Vec<R>> {
375 Ok(sql(format!(
376 "SELECT {} FROM {} WHERE {} = ? ORDER BY {}",
377 quote(self.right),
378 quote(self.table),
379 quote(self.left),
380 quote(self.right)
381 ))
382 .bind(left)
383 .scalars(db)
384 .await?)
385 }
386
387 pub async fn attach<'c>(
390 &self,
391 db: impl Executor<'c>,
392 left: L,
393 rights: impl IntoIterator<Item = R>,
394 ) -> Result<u64> {
395 let mut conn = db.into_conn();
396 let mut added = 0;
397 for right in rights {
398 added += u64::from(self.insert_link(&mut conn, &left, &right, &[]).await?);
399 }
400 Ok(added)
401 }
402
403 pub async fn attach_with<'c>(
417 &self,
418 db: impl Executor<'c>,
419 left: L,
420 right: R,
421 data: PivotData<'_>,
422 ) -> Result<bool> {
423 let mut conn = db.into_conn();
424 self.insert_link(&mut conn, &left, &right, data).await
425 }
426
427 pub async fn update_pivot<'c>(
430 &self,
431 db: impl Executor<'c>,
432 left: L,
433 right: R,
434 data: PivotData<'_>,
435 ) -> Result<bool> {
436 let mut sets = Vec::new();
437 let mut values = Vec::new();
438 for (column, value) in data {
439 sets.push(format!("{} = ?", quote(column)));
440 values.push(value.to_db_value());
441 }
442 if self.timestamps {
443 sets.push(format!("{} = ?", quote("updated_at")));
444 values.push(super::ToDbValue::to_db_value(&super::now()));
445 }
446 if sets.is_empty() {
447 return Ok(false);
448 }
449 let changed = sql(format!(
450 "UPDATE {} SET {} WHERE {} = ? AND {} = ?",
451 quote(self.table),
452 sets.join(", "),
453 quote(self.left),
454 quote(self.right)
455 ))
456 .bind_all(values)
457 .bind(left)
458 .bind(right)
459 .execute(db)
460 .await?;
461 Ok(changed > 0)
462 }
463
464 pub async fn toggle(
467 &self,
468 db: &Db,
469 left: L,
470 rights: impl IntoIterator<Item = R>,
471 ) -> Result<(Vec<R>, Vec<R>)> {
472 let mut tx = db.begin().await?;
473 let current = self.ids(&mut tx, left.clone()).await?;
474 let (detach, attach): (Vec<R>, Vec<R>) =
475 rights.into_iter().partition(|id| current.contains(id));
476 self.detach(&mut tx, left.clone(), detach.iter().cloned())
477 .await?;
478 self.attach(&mut tx, left, attach.iter().cloned()).await?;
479 tx.commit().await?;
480 Ok((attach, detach))
481 }
482
483 pub async fn detach<'c>(
485 &self,
486 db: impl Executor<'c>,
487 left: L,
488 rights: impl IntoIterator<Item = R>,
489 ) -> Result<u64> {
490 let rights: Vec<R> = rights.into_iter().collect();
491 if rights.is_empty() {
492 return Ok(0);
493 }
494 let marks = vec!["?"; rights.len()].join(", ");
495 Ok(sql(format!(
496 "DELETE FROM {} WHERE {} = ? AND {} IN ({marks})",
497 quote(self.table),
498 quote(self.left),
499 quote(self.right)
500 ))
501 .bind(left)
502 .bind_all(rights.iter().map(ToDbValue::to_db_value))
503 .execute(db)
504 .await?)
505 }
506
507 pub async fn sync(&self, db: &Db, left: L, rights: impl IntoIterator<Item = R>) -> Result {
510 let wanted: Vec<R> = rights.into_iter().collect();
511 let mut tx = db.begin().await?;
512 let current = self.ids(&mut tx, left.clone()).await?;
513 let gone: Vec<R> = current
514 .into_iter()
515 .filter(|id| !wanted.contains(id))
516 .collect();
517 self.detach(&mut tx, left.clone(), gone).await?;
518 self.attach(&mut tx, left, wanted).await?;
519 tx.commit().await?;
520 Ok(())
521 }
522
523 pub fn load<'a, T: Model<Key = R> + Clone>(
525 &'a self,
526 db: &'a Db,
527 lefts: impl IntoIterator<Item = L>,
528 ) -> impl Future<Output = Result<HashMap<L, Vec<T>>>> + Send + 'a {
529 let lefts: Vec<L> = lefts.into_iter().collect();
530 self.load_ids(db, lefts)
531 }
532
533 async fn load_ids<T: Model<Key = R> + Clone>(
534 &self,
535 db: &Db,
536 lefts: Vec<L>,
537 ) -> Result<HashMap<L, Vec<T>>> {
538 let mut grouped: HashMap<L, Vec<T>> = HashMap::new();
539 if lefts.is_empty() {
540 return Ok(grouped);
541 }
542 let marks = vec!["?"; lefts.len()].join(", ");
543 let links: Vec<(L, R)> = sql(format!(
544 "SELECT {}, {} FROM {} WHERE {} IN ({marks})",
545 quote(self.left),
546 quote(self.right),
547 quote(self.table),
548 quote(self.left)
549 ))
550 .bind_all(lefts.iter().map(ToDbValue::to_db_value))
551 .fetch_as(db)
552 .await?;
553 let mut rights: Vec<R> = links.iter().map(|(_, right)| right.clone()).collect();
554 rights.sort_unstable();
555 rights.dedup();
556 let models: HashMap<R, T> = T::find_many(db, rights)
557 .await?
558 .into_iter()
559 .map(|model| (model.id(), model))
560 .collect();
561 for (left, right) in links {
562 if let Some(model) = models.get(&right) {
563 grouped.entry(left).or_default().push(model.clone());
564 }
565 }
566 Ok(grouped)
567 }
568
569 pub fn load_with_pivot<'a, T: Model<Key = R> + Clone, D: super::FromRow + Send + 'a>(
587 &'a self,
588 db: &'a Db,
589 lefts: impl IntoIterator<Item = L>,
590 ) -> impl Future<Output = Result<HashMap<L, Vec<(T, D)>>>> + Send + 'a {
591 let lefts: Vec<L> = lefts.into_iter().collect();
592 async move {
593 let mut grouped: HashMap<L, Vec<(T, D)>> = HashMap::new();
594 if lefts.is_empty() {
595 return Ok(grouped);
596 }
597 let marks = vec!["?"; lefts.len()].join(", ");
598 let rows = sql(format!(
599 "SELECT * FROM {} WHERE {} IN ({marks})",
600 quote(self.table),
601 quote(self.left)
602 ))
603 .bind_all(lefts.iter().map(ToDbValue::to_db_value))
604 .fetch_all(db)
605 .await?;
606 let mut links = Vec::with_capacity(rows.len());
607 for row in &rows {
608 let left: L = row.try_get(self.left)?;
609 let right: R = row.try_get(self.right)?;
610 links.push((left, right, D::from_row(row)?));
611 }
612 let mut rights: Vec<R> = links.iter().map(|(_, right, _)| right.clone()).collect();
613 rights.sort_unstable();
614 rights.dedup();
615 let models: HashMap<R, T> = T::find_many(db, rights)
616 .await?
617 .into_iter()
618 .map(|model| (model.id(), model))
619 .collect();
620 for (left, right, data) in links {
621 if let Some(model) = models.get(&right) {
622 grouped.entry(left).or_default().push((model.clone(), data));
623 }
624 }
625 Ok(grouped)
626 }
627 }
628
629 pub fn load_for<'a, T: Model<Key = R> + Clone, P: Model<Key = L>>(
631 &'a self,
632 db: &'a Db,
633 parents: &[P],
634 ) -> impl Future<Output = Result<HashMap<L, Vec<T>>>> + Send + 'a {
635 let ids: Vec<L> = parents.iter().map(Model::id).collect();
636 self.load_ids(db, ids)
637 }
638}
639
640#[derive(Debug, Clone, Copy)]
679pub struct Morph {
680 type_column: &'static str,
681 id_column: &'static str,
682}
683
684impl Morph {
685 pub const fn new(type_column: &'static str, id_column: &'static str) -> Self {
687 Self {
688 type_column,
689 id_column,
690 }
691 }
692
693 pub fn of<C: Model, P: Model>(&self, parent: &P, children: Query<C>) -> Query<C> {
695 children
696 .where_eq(self.type_column, P::TABLE)
697 .where_eq(self.id_column, parent.id())
698 }
699
700 pub fn load_many<'a, C: Model, P: Model>(
704 &self,
705 db: &'a Db,
706 parents: &[P],
707 children: Query<C>,
708 foreign_key: impl Fn(&C) -> P::Key + Send + 'a,
709 ) -> impl Future<Output = Result<HashMap<P::Key, Vec<C>>>> + Send + 'a {
710 let children = children.where_eq(self.type_column, P::TABLE);
711 has_many(db, parents, children, self.id_column, foreign_key)
712 }
713
714 pub fn count_many<'a, C: Model, P: Model>(
717 &self,
718 db: &'a Db,
719 parents: &[P],
720 children: Query<C>,
721 ) -> impl Future<Output = Result<HashMap<P::Key, i64>>> + Send + 'a {
722 let children = children.where_eq(self.type_column, P::TABLE);
723 count_many(db, parents, children, self.id_column)
724 }
725
726 pub fn parents<'a, P: Model, C>(
730 &self,
731 db: &'a Db,
732 children: &[C],
733 parent: impl Fn(&C) -> (String, P::Key),
734 ) -> impl Future<Output = Result<HashMap<P::Key, P>>> + Send + 'a {
735 let mut ids: Vec<P::Key> = children
736 .iter()
737 .filter_map(|child| {
738 let (kind, id) = parent(child);
739 (kind == P::TABLE).then_some(id)
740 })
741 .collect();
742 ids.sort_unstable();
743 ids.dedup();
744 async move {
745 if ids.is_empty() {
746 return Ok(HashMap::new());
747 }
748 Ok(P::find_many(db, ids)
749 .await?
750 .into_iter()
751 .map(|parent| (parent.id(), parent))
752 .collect())
753 }
754 }
755}