use super::{List, Query};
use crate::{Executor, Result, schema::Load};
use toasty_core::stmt;
#[derive(Debug)]
pub struct Paginate<M> {
query: Query<List<M>>,
reverse: bool,
}
impl<M> Paginate<M> {
pub fn new(mut query: Query<List<M>>, per_page: usize) -> Self {
assert!(
query.untyped.limit.is_none(),
"pagination requires no limit clause"
);
assert!(
query.untyped.order_by.is_some(),
"pagination requires an order_by clause"
);
let per_page = i64::try_from(per_page).expect("per_page exceeds i64::MAX");
query.untyped.limit = Some(stmt::Limit::Cursor(stmt::LimitCursor {
page_size: stmt::Value::from(per_page).into(),
after: None,
}));
Self {
query,
reverse: false,
}
}
pub fn after(mut self, key: impl Into<stmt::Expr>) -> Self {
let Some(stmt::Limit::Cursor(cursor)) = self.query.untyped.limit.as_mut() else {
panic!("pagination requires a cursor limit clause");
};
cursor.after = Some(key.into());
self.reverse = false;
self
}
pub fn before(mut self, key: impl Into<stmt::Expr>) -> Self {
let Some(stmt::Limit::Cursor(cursor)) = self.query.untyped.limit.as_mut() else {
panic!("pagination requires a cursor limit clause");
};
cursor.after = Some(key.into());
self.reverse = true;
self
}
}
impl<M: Load> Paginate<M> {
pub async fn exec(mut self, executor: &mut dyn Executor) -> Result<super::Page<M::Output>> {
let original_query = self.query.untyped.clone();
if self.reverse {
let Some(order_by) = self.query.untyped.order_by.as_mut() else {
panic!("pagination requires order by clause");
};
order_by.reverse();
}
let response = executor
.exec_untyped(stmt::Statement::Query(self.query.untyped.clone()))
.await?;
let stmt::Value::List(mut items) = response.values.collect_as_value().await? else {
return Err(crate::Error::invalid_result(
"paginated query expected a list of rows",
));
};
if self.reverse {
items.reverse();
}
let loaded_items: Vec<M::Output> = items.into_iter().map(M::load).collect::<Result<_>>()?;
let (next_cursor, prev_cursor) = if self.reverse {
(response.prev_cursor, response.next_cursor)
} else {
(response.next_cursor, response.prev_cursor)
};
Ok(super::Page::new(
loaded_items,
Query::from_untyped(original_query),
next_cursor,
prev_cursor,
))
}
}
impl<M> From<Query<List<M>>> for Paginate<M> {
fn from(value: Query<List<M>>) -> Self {
assert!(
value.untyped.limit.is_some(),
"pagination requires a limit clause"
);
assert!(
value.untyped.order_by.is_some(),
"pagination requires an order_by clause"
);
Paginate {
query: value,
reverse: false,
}
}
}