use crate::context::{ContextInner, DjogiContext};
use crate::error::DjogiError;
use crate::pg::connection::PgConnection;
use crate::pg::cursor::{PgCursor, cursor_close, cursor_fetch};
use crate::pg::decode::FromPgRow;
use futures::Stream;
use postgres_types::ToSql;
use std::collections::VecDeque;
use std::pin::Pin;
use tokio_postgres::Row;
pub const DEFAULT_FETCH_SIZE: u32 = 1000;
struct CursorDriver<'ctx> {
cursor_name: String,
conn: &'ctx mut PgConnection,
buffer: VecDeque<Row>,
fetch_size: u32,
exhausted: bool,
closed: bool,
}
impl<'ctx> CursorDriver<'ctx> {
fn new(cursor_name: String, conn: &'ctx mut PgConnection, fetch_size: u32) -> Self {
CursorDriver {
cursor_name,
conn,
buffer: VecDeque::new(),
fetch_size,
exhausted: false,
closed: false,
}
}
async fn next_row(&mut self) -> Option<Result<Row, DjogiError>> {
loop {
if self.closed {
return None;
}
if let Some(row) = self.buffer.pop_front() {
return Some(Ok(row));
}
if self.exhausted {
self.closed = true;
let _ = cursor_close(&self.cursor_name, self.conn).await;
return None;
}
let fetch_size = self.fetch_size;
match cursor_fetch(&self.cursor_name, self.conn, fetch_size).await {
Ok(rows) => {
let got = rows.len() as u32;
if got < self.fetch_size {
self.exhausted = true;
}
self.buffer.extend(rows);
}
Err(e) => return Some(Err(e)),
}
}
}
}
pub struct ModelCursorStream<'ctx, T> {
inner: Pin<Box<dyn Stream<Item = Result<T, DjogiError>> + Send + 'ctx>>,
}
impl<'ctx, T: FromPgRow + Send + 'ctx> ModelCursorStream<'ctx, T> {
fn new(cursor_name: String, conn: &'ctx mut PgConnection, fetch_size: u32) -> Self {
let driver = CursorDriver::new(cursor_name, conn, fetch_size);
let inner = futures::stream::unfold(driver, |mut driver| async move {
match driver.next_row().await {
Some(Ok(row)) => Some((T::from_pg_row(&row), driver)),
Some(Err(e)) => Some((Err(e), driver)),
None => None,
}
});
ModelCursorStream {
inner: Box::pin(inner),
}
}
}
impl<'ctx, T> Stream for ModelCursorStream<'ctx, T> {
type Item = Result<T, DjogiError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
pub struct RawCursorStream<'ctx> {
inner: Pin<Box<dyn Stream<Item = Result<Row, DjogiError>> + Send + 'ctx>>,
}
impl<'ctx> RawCursorStream<'ctx> {
fn new(cursor_name: String, conn: &'ctx mut PgConnection, fetch_size: u32) -> Self {
let driver = CursorDriver::new(cursor_name, conn, fetch_size);
let inner = futures::stream::unfold(driver, |mut driver| async move {
driver.next_row().await.map(|result| (result, driver))
});
RawCursorStream {
inner: Box::pin(inner),
}
}
}
impl<'ctx> Stream for RawCursorStream<'ctx> {
type Item = Result<Row, DjogiError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.inner.as_mut().poll_next(cx)
}
}
pub async fn build_model_stream<'ctx, T: FromPgRow + Send + 'ctx>(
ctx: &'ctx mut DjogiContext,
sql: &str,
params: &[&(dyn ToSql + Sync)],
fetch_size: u32,
) -> Result<ModelCursorStream<'ctx, T>, DjogiError> {
let conn = require_transaction(ctx)?;
let cursor = PgCursor::declare(conn, sql, params).await?;
let name = cursor.name().to_owned();
Ok(ModelCursorStream::new(name, conn, fetch_size))
}
pub async fn build_raw_stream<'ctx>(
ctx: &'ctx mut DjogiContext,
sql: &str,
params: &[&(dyn ToSql + Sync)],
fetch_size: u32,
) -> Result<RawCursorStream<'ctx>, DjogiError> {
let conn = require_transaction(ctx)?;
let cursor = PgCursor::declare(conn, sql, params).await?;
let name = cursor.name().to_owned();
Ok(RawCursorStream::new(name, conn, fetch_size))
}
fn require_transaction(ctx: &mut DjogiContext) -> Result<&mut PgConnection, DjogiError> {
if let Some(err) = ctx.transaction_poison_error() {
return Err(err);
}
match ctx.inner_mut() {
ContextInner::Transaction(conn) => Ok(conn),
ContextInner::Pool(_) => Err(DjogiError::StreamOutsideTransaction),
}
}