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