use crate::parser::ParsedModel;
use proc_macro2::TokenStream;
use quote::quote;
#[cfg_attr(test, mutants::skip)]
pub fn generate_chunk_methods(parsed: &ParsedModel) -> Vec<TokenStream> {
let name = &parsed.name;
let table_name = &parsed.table_name;
let default_order = format!("{table_name}.id ASC");
vec![quote! {
#[rullst_orm::_tracing::instrument(
name = "rullst.orm.query",
target = "rullst_orm",
skip(self, handler),
fields(
orm.model = stringify!(#name),
orm.table = #table_name,
orm.operation = "chunk"
)
)]
pub async fn chunk<F, Fut>(&self, size: usize, mut handler: F) -> Result<(), rullst_orm::Error>
where
F: FnMut(Vec<#name>) -> Fut + Send,
Fut: std::future::Future<Output = ()> + Send,
{
if size == 0 {
return Err(rullst_orm::Error::Validation(
"chunk() requires a size greater than zero".to_string()
));
}
let mut remaining = self.__rullst_explicit_limit();
let mut offset = self.offset.unwrap_or(0);
let mut builder = self.clone();
if builder.order_by.is_none() {
builder.order_bindings.clear();
builder.order_by = Some(#default_order.to_string());
}
while let Some(page) = Self::__rullst_chunk_page(size, remaining) {
builder.limit = Some(page);
builder.offset = Some(offset);
let results = builder.get().await?;
let count = results.len();
if count == 0 { break; }
handler(results).await;
if count < page { break; }
remaining = remaining.map(|left| left.saturating_sub(count));
offset = offset.checked_add(count).ok_or_else(|| {
rullst_orm::Error::Validation(
"chunk() offset exceeds the supported range".to_string()
)
})?;
}
Ok(())
}
pub async fn chunk_with_tx<F, Fut>(&self, size: usize, tx: &mut rullst_orm::db::Transaction<'static>, mut handler: F) -> Result<(), rullst_orm::Error>
where
F: FnMut(Vec<#name>) -> Fut + Send,
Fut: std::future::Future<Output = ()> + Send,
{
if size == 0 {
return Err(rullst_orm::Error::Validation(
"chunk_with_tx() requires a size greater than zero".to_string()
));
}
let mut remaining = self.__rullst_explicit_limit();
let mut offset = self.offset.unwrap_or(0);
let mut builder = self.clone();
if builder.order_by.is_none() {
builder.order_bindings.clear();
builder.order_by = Some(#default_order.to_string());
}
while let Some(page) = Self::__rullst_chunk_page(size, remaining) {
builder.limit = Some(page);
builder.offset = Some(offset);
let results = builder.get_with_tx(tx).await?;
let count = results.len();
if count == 0 { break; }
handler(results).await;
if count < page { break; }
remaining = remaining.map(|left| left.saturating_sub(count));
offset = offset.checked_add(count).ok_or_else(|| {
rullst_orm::Error::Validation(
"chunk_with_tx() offset exceeds the supported range".to_string()
)
})?;
}
Ok(())
}
#[rullst_orm::_tracing::instrument(
name = "rullst.orm.query",
target = "rullst_orm",
skip(self, handler),
fields(
orm.model = stringify!(#name),
orm.table = #table_name,
orm.operation = "chunk_by_id"
)
)]
pub async fn chunk_by_id<F, Fut>(&self, size: usize, mut handler: F) -> Result<(), rullst_orm::Error>
where
F: FnMut(Vec<#name>) -> Fut + Send,
Fut: std::future::Future<Output = Result<(), rullst_orm::Error>> + Send,
{
if size == 0 {
return Err(rullst_orm::Error::Validation(
"chunk_by_id() requires a size greater than zero".to_string()
));
}
let mut remaining = self.__rullst_explicit_limit();
let mut cursor: Option<i32> = None;
while let Some(page) = Self::__rullst_chunk_page(size, remaining) {
let mut builder = self.clone();
builder.order_bindings.clear();
builder.order_by = Some(#default_order.to_string());
builder.freeze_scope();
builder.limit = Some(page);
builder.offset = if cursor.is_none() { self.offset } else { None };
if let Some(last_seen_id) = cursor {
builder = builder.__rullst_where_own("id", ">", last_seen_id);
}
let results = builder.get().await?;
let count = results.len();
if count == 0 { break; }
cursor = results.last().map(|model| model.id);
handler(results).await?;
if count < page { break; }
remaining = remaining.map(|left| left.saturating_sub(count));
}
Ok(())
}
pub async fn chunk_by_id_with_tx<F, Fut>(&self, size: usize, tx: &mut rullst_orm::db::Transaction<'static>, mut handler: F) -> Result<(), rullst_orm::Error>
where
F: FnMut(Vec<#name>) -> Fut + Send,
Fut: std::future::Future<Output = Result<(), rullst_orm::Error>> + Send,
{
if size == 0 {
return Err(rullst_orm::Error::Validation(
"chunk_by_id_with_tx() requires a size greater than zero".to_string()
));
}
let mut remaining = self.__rullst_explicit_limit();
let mut cursor: Option<i32> = None;
while let Some(page) = Self::__rullst_chunk_page(size, remaining) {
let mut builder = self.clone();
builder.order_bindings.clear();
builder.order_by = Some(#default_order.to_string());
builder.freeze_scope();
builder.limit = Some(page);
builder.offset = if cursor.is_none() { self.offset } else { None };
if let Some(last_seen_id) = cursor {
builder = builder.__rullst_where_own("id", ">", last_seen_id);
}
let results = builder.get_with_tx(tx).await?;
let count = results.len();
if count == 0 { break; }
cursor = results.last().map(|model| model.id);
handler(results).await?;
if count < page { break; }
remaining = remaining.map(|left| left.saturating_sub(count));
}
Ok(())
}
fn __rullst_chunk_page(size: usize, remaining: Option<usize>) -> Option<usize> {
match remaining {
Some(0) => None,
Some(left) => Some(size.min(left)),
None => Some(size),
}
}
}]
}