use crate::model::dto::Page;
use diesel::pg::Pg;
use diesel::query_builder::{AstPass, Query, QueryFragment};
use diesel::sql_types::{BigInt, Integer};
use diesel::{QueryId, QueryResult};
use diesel_async::methods::LoadQuery;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
use std::cmp::max;
pub const DEFAULT_PER_PAGE: i32 = 10;
pub const MIN_PAGE: i32 = 1;
pub const MIN_PER_PAGE: i32 = 1;
#[derive(Debug, Clone, Copy, QueryId)]
pub struct PaginatedQuery<T> {
query: T,
page: i32,
per_page: i32,
offset: i32,
}
pub trait Paginate: Sized {
fn paginate(self, page: Option<i32>) -> PaginatedQuery<Self>;
}
impl<T> Paginate for T {
fn paginate(self, page: Option<i32>) -> PaginatedQuery<Self> {
let actual_page = max(page.unwrap_or(MIN_PAGE), MIN_PAGE);
PaginatedQuery {
query: self,
per_page: DEFAULT_PER_PAGE,
page: actual_page,
offset: (actual_page - 1) * DEFAULT_PER_PAGE,
}
}
}
impl<T> PaginatedQuery<T> {
#[must_use]
pub fn per_page(self, per_page: Option<i32>) -> Self {
let actual_per_page = max(per_page.unwrap_or(DEFAULT_PER_PAGE), MIN_PER_PAGE);
Self {
per_page: actual_per_page,
offset: (self.page - 1) * actual_per_page,
..self
}
}
pub async fn load_page<'query, 'conn, U>(
self,
conn: &'conn mut AsyncPgConnection,
) -> QueryResult<Page<U>>
where
T: Send,
U: Send,
Self: LoadQuery<'query, AsyncPgConnection, (U, i64)> + 'query,
{
let page = self.page;
let per_page = self.per_page;
let query_result = self.load::<(U, i64)>(conn).await?;
let total = query_result.get(0).map_or(0, |x| x.1);
let results = query_result.into_iter().map(|x| x.0).collect();
let extra_page = match total % i64::from(per_page) {
0 => 0,
_ => 1,
};
#[allow(clippy::cast_possible_truncation, clippy::integer_division)]
let total_pages = (total / i64::from(per_page) + extra_page) as i32;
Ok(Page {
results,
page,
per_page,
total_pages,
})
}
}
impl<T: Query> Query for PaginatedQuery<T> {
type SqlType = (T::SqlType, BigInt);
}
impl<T> QueryFragment<Pg> for PaginatedQuery<T>
where
T: QueryFragment<Pg>,
{
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
out.push_sql("SELECT *, COUNT(*) OVER () FROM (");
self.query.walk_ast(out.reborrow())?;
out.push_sql(") t LIMIT ");
out.push_bind_param::<Integer, _>(&self.per_page)?;
out.push_sql(" OFFSET ");
out.push_bind_param::<Integer, _>(&self.offset)?;
Ok(())
}
}