use deadpool::managed::Object;
use tokio::sync::mpsc;
use crate::error::{Error, Result};
use crate::native::decode::DecodedBlock;
use crate::tcp::pool::TcpConnectionManager;
use crate::tcp::reader::ServerPacket;
#[non_exhaustive]
pub struct TcpRawCursor {
rx: mpsc::Receiver<Result<ServerPacket>>,
pool_slot: Option<Object<TcpConnectionManager>>,
done: bool,
saw_eos: bool,
}
impl TcpRawCursor {
pub(crate) fn from_receiver(rx: mpsc::Receiver<Result<ServerPacket>>) -> Self {
Self {
rx,
pool_slot: None,
done: false,
saw_eos: false,
}
}
pub(crate) fn hold_pool_slot(&mut self, slot: Object<TcpConnectionManager>) {
self.pool_slot = Some(slot);
}
pub async fn next_block(&mut self) -> Result<Option<DecodedBlock>> {
if self.done {
return Ok(None);
}
loop {
match self.rx.recv().await {
None => {
self.done = true;
if self.saw_eos {
return Ok(None);
}
return Err(Error::Custom(
"tcp: connection actor exited before EndOfStream".into(),
));
}
Some(Err(e)) => {
self.done = true;
return Err(e);
}
Some(Ok(ServerPacket::EndOfStream)) => {
self.saw_eos = true;
self.done = true;
return Ok(None);
}
Some(Ok(ServerPacket::DataBlock(block))) => {
return Ok(Some(block));
}
Some(Ok(ServerPacket::Data {
num_rows, columns, ..
})) => {
return Ok(Some(DecodedBlock {
columns: Vec::new(),
schema: columns,
num_rows,
}));
}
Some(Ok(_)) => {}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::native::decode::DecodedColumn;
use crate::tcp::reader::ServerPacket;
static_assertions::assert_impl_all!(TcpRawCursor: Send);
#[tokio::test]
async fn raw_cursor_terminates_on_end_of_stream() {
let (tx, rx) = mpsc::channel(4);
let mut cur = TcpRawCursor::from_receiver(rx);
tx.send(Ok(ServerPacket::EndOfStream)).await.unwrap();
assert!(cur.next_block().await.unwrap().is_none());
assert!(cur.next_block().await.unwrap().is_none());
}
#[tokio::test]
async fn raw_cursor_yields_data_block_then_eos() {
let (tx, rx) = mpsc::channel(4);
let mut cur = TcpRawCursor::from_receiver(rx);
let block = DecodedBlock {
columns: vec![DecodedColumn::UInt64(vec![1, 2, 3])],
schema: vec![("n".into(), "UInt64".into())],
num_rows: 3,
};
tx.send(Ok(ServerPacket::DataBlock(block))).await.unwrap();
tx.send(Ok(ServerPacket::EndOfStream)).await.unwrap();
let first = cur.next_block().await.unwrap().expect("block");
assert_eq!(first.num_rows, 3);
match &first.columns[0] {
DecodedColumn::UInt64(v) => assert_eq!(v, &vec![1u64, 2, 3]),
other => panic!("expected UInt64, got {other:?}"),
}
assert!(cur.next_block().await.unwrap().is_none());
}
#[tokio::test]
async fn raw_cursor_surfaces_server_error() {
let (tx, rx) = mpsc::channel(4);
let mut cur = TcpRawCursor::from_receiver(rx);
let err = Error::ServerException {
code: 60,
name: Some("DB::Exception".into()),
message: "table not found".into(),
stack_trace: None,
};
tx.send(Err(err)).await.unwrap();
let result = cur.next_block().await;
match result {
Err(Error::ServerException { code, .. }) => assert_eq!(code, 60),
other => panic!("expected ServerException, got {other:?}"),
}
assert!(cur.next_block().await.unwrap().is_none());
}
#[tokio::test]
async fn raw_cursor_skips_protocol_chatter() {
use crate::tcp::protocol::{ProfileInfo, Progress};
let (tx, rx) = mpsc::channel(8);
let mut cur = TcpRawCursor::from_receiver(rx);
tx.send(Ok(ServerPacket::Progress(Progress {
rows_read: 1,
bytes_read: 8,
total_rows_to_read: 0,
written_rows: 0,
written_bytes: 0,
})))
.await
.unwrap();
tx.send(Ok(ServerPacket::ProfileInfo(ProfileInfo {
rows: 1,
blocks: 1,
bytes: 8,
applied_limit: false,
rows_before_limit: 0,
})))
.await
.unwrap();
tx.send(Ok(ServerPacket::EndOfStream)).await.unwrap();
assert!(cur.next_block().await.unwrap().is_none());
}
#[tokio::test]
async fn raw_cursor_errors_when_the_actor_dies_before_end_of_stream() {
let (tx, rx) = mpsc::channel::<Result<ServerPacket>>(4);
let mut cur = TcpRawCursor::from_receiver(rx);
drop(tx);
let err = cur
.next_block()
.await
.expect_err("a closed channel without EndOfStream is an error");
match err {
Error::Custom(msg) => assert!(
msg.contains("before EndOfStream"),
"expected a truncated-stream message, got {msg}"
),
other => panic!("expected Custom, got {other:?}"),
}
assert!(cur.next_block().await.unwrap().is_none());
}
}