use std::collections::VecDeque;
use std::marker::PhantomData;
use super::{
select_rows_pool_with_related, ExecError, LoadRelated, MaybeMyFromRow, MaybeMyLoadRelated,
MaybePgFromRow, MaybeSqliteFromRow, MaybeSqliteLoadRelated,
};
use crate::core::Model;
use crate::sql::Pool;
pub struct ChunkedIter<T> {
pub(super) query: crate::core::SelectQuery,
pub(super) chunk_size: i64,
pub(super) offset: i64,
pub(super) end: Option<i64>,
pub(super) exhausted: bool,
pub(super) buffer: VecDeque<T>,
pub(super) seen: i64,
pub(super) _model: PhantomData<fn() -> T>,
}
impl<T> ChunkedIter<T>
where
T: Model
+ MaybePgFromRow
+ MaybeMyFromRow
+ MaybeSqliteFromRow
+ LoadRelated
+ MaybeMyLoadRelated
+ MaybeSqliteLoadRelated
+ Send
+ Unpin,
{
pub async fn next_chunk(&mut self, pool: &Pool) -> Result<Option<Vec<T>>, ExecError> {
if !self.buffer.is_empty() {
let buffered: Vec<T> = self.buffer.drain(..).collect();
self.seen += buffered.len() as i64;
return Ok(Some(buffered));
}
match self.fetch_next_chunk(pool).await? {
Some(rows) => {
self.seen += rows.len() as i64;
Ok(Some(rows))
}
None => Ok(None),
}
}
pub async fn next_row(&mut self, pool: &Pool) -> Result<Option<T>, ExecError> {
if let Some(row) = self.buffer.pop_front() {
self.seen += 1;
return Ok(Some(row));
}
match self.fetch_next_chunk(pool).await? {
Some(rows) => {
self.buffer.extend(rows);
let row = self.buffer.pop_front();
if row.is_some() {
self.seen += 1;
}
Ok(row)
}
None => Ok(None),
}
}
async fn fetch_next_chunk(&mut self, pool: &Pool) -> Result<Option<Vec<T>>, ExecError> {
if self.exhausted {
return Ok(None);
}
let size = self.end.map_or(self.chunk_size, |end| {
self.chunk_size.min(end - self.offset)
});
if size <= 0 {
self.exhausted = true;
return Ok(None);
}
let mut q = self.query.clone();
let (limit, offset) = if q.compound.is_empty() {
(&mut q.limit, &mut q.offset)
} else {
(&mut q.compound_limit, &mut q.compound_offset)
};
*limit = Some(size);
*offset = Some(self.offset);
let rows = select_rows_pool_with_related::<T>(pool, &q).await?;
let n = rows.len() as i64;
if rows.is_empty() {
self.exhausted = true;
return Ok(None);
}
if n < size {
self.exhausted = true;
}
self.offset += n;
Ok(Some(rows))
}
#[must_use]
pub fn rows_seen(&self) -> i64 {
self.seen
}
#[must_use]
pub fn is_exhausted(&self) -> bool {
self.exhausted && self.buffer.is_empty()
}
}