1use std::marker::PhantomData;
4
5use sqlx::FromRow;
6
7use crate::pool::Pool;
8use crate::query::QueryBuilder;
9use crate::Error;
10
11pub trait Model: Sized + Send + Sync + Unpin + 'static
12where
13 for<'r> Self: FromRow<'r, sqlx::postgres::PgRow>,
14{
15 type PrimaryKey: sqlx::Type<sqlx::Postgres>
16 + for<'q> sqlx::Encode<'q, sqlx::Postgres>
17 + Send
18 + Sync
19 + Clone
20 + 'static;
21
22 const TABLE: &'static str;
24
25 const PK_COLUMN: &'static str = "id";
27
28 const COLUMNS: &'static [&'static str];
30
31 fn primary_key(&self) -> &Self::PrimaryKey;
33
34 fn query() -> QueryBuilder<Self> {
36 QueryBuilder::new()
37 }
38
39 fn find(
41 pool: &Pool,
42 id: Self::PrimaryKey,
43 ) -> futures::future::BoxFuture<'_, Result<Option<Self>, Error>> {
44 Box::pin(async move {
45 let sql = format!(
46 "SELECT {} FROM {} WHERE {} = $1 LIMIT 1",
47 Self::COLUMNS.join(", "),
48 Self::TABLE,
49 Self::PK_COLUMN,
50 );
51 let result = sqlx::query_as::<_, Self>(&sql)
52 .bind(id)
53 .fetch_optional(pool)
54 .await?;
55 Ok(result)
56 })
57 }
58
59 fn all(pool: &Pool) -> futures::future::BoxFuture<'_, Result<Vec<Self>, Error>> {
61 Box::pin(async move {
62 let sql = format!(
63 "SELECT {} FROM {}",
64 Self::COLUMNS.join(", "),
65 Self::TABLE
66 );
67 let rows = sqlx::query_as::<_, Self>(&sql).fetch_all(pool).await?;
68 Ok(rows)
69 })
70 }
71}
72
73pub struct Loaded<M: Model, R = ()> {
76 pub items: Vec<M>,
77 pub _relations: PhantomData<R>,
78}
79
80impl<M: Model, R> Loaded<M, R> {
81 pub fn new(items: Vec<M>) -> Self {
82 Self {
83 items,
84 _relations: PhantomData,
85 }
86 }
87
88 pub fn into_inner(self) -> Vec<M> {
89 self.items
90 }
91
92 pub fn iter(&self) -> std::slice::Iter<'_, M> {
93 self.items.iter()
94 }
95}