use super::base::BaseWriter;
use super::format::DataFormat;
use crate::Result;
use crate::model::{FinalizeWriteStreamResponse, FlushRowsResponse};
use crate::write::builder::AppendWithOffset;
use crate::write::transport::Transport;
use std::sync::Arc;
#[derive(Debug)]
pub struct BufferedWriter<F> {
pub(crate) inner: BaseWriter<F>,
}
impl<F> BufferedWriter<F>
where
F: DataFormat,
{
pub(crate) fn new(inner: Arc<Transport>, write_stream: String, format: F) -> Self {
Self {
inner: BaseWriter::new(inner, write_stream, format),
}
}
pub fn write_stream(&self) -> &str {
&self.inner.write_stream
}
pub fn append(&self, rows: F::Rows) -> AppendWithOffset {
AppendWithOffset::new(
self.inner.runner.req_tx.clone(),
self.inner.append_request(rows),
)
}
pub async fn flush(&self, offset: i64) -> Result<FlushRowsResponse> {
self.inner
.client
.flush_rows()
.set_write_stream(&self.inner.write_stream)
.set_offset(offset)
.send()
.await
}
pub async fn finalize(&self) -> Result<FinalizeWriteStreamResponse> {
self.inner.finalize().await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::AppendError;
use crate::write::test::*;
use bigquery_grpc_mock::{MockBigQueryWrite, start};
use gaxi::grpc::tonic::Response as TonicResponse;
use tokio::sync::mpsc;
#[tokio::test]
async fn basic_success() -> anyhow::Result<()> {
let (response_tx, response_rx) = mpsc::channel(10);
let mut mock = MockBigQueryWrite::new();
mock.expect_append_rows()
.return_once(|_| Ok(TonicResponse::from(response_rx)));
mock.expect_flush_rows()
.return_once(|req| {
assert_eq!(req.get_ref().offset, Some(3));
assert_eq!(req.get_ref().write_stream, write_stream());
Ok(TonicResponse::new(
bigquery_grpc_mock::google::cloud::bigquery::storage::v1::FlushRowsResponse::default()
))
});
mock.expect_finalize_write_stream()
.return_once(|req| {
assert_eq!(req.get_ref().name, write_stream());
Ok(TonicResponse::new(
bigquery_grpc_mock::google::cloud::bigquery::storage::v1::FinalizeWriteStreamResponse::default()
))
});
let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
let transport = Arc::new(test_transport(endpoint).await?);
let writer = BufferedWriter::new(transport, write_stream(), format());
assert_eq!(writer.write_stream(), write_stream());
response_tx.send(Ok(convert(&test_response(1)))).await?;
let resp = writer.append(rows(1)).send().await?;
assert_eq!(resp.offset, Some(1));
response_tx.send(Ok(convert(&test_response(2)))).await?;
let resp = writer.append(rows(2)).send().await?;
assert_eq!(resp.offset, Some(2));
response_tx.send(Ok(convert(&test_response(3)))).await?;
let resp = writer.append(rows(3)).send().await?;
assert_eq!(resp.offset, Some(3));
drop(response_tx);
let err = writer.append(rows(4)).send().await.expect_err("channel");
assert!(matches!(err, AppendError::UnexpectedEndOfStream));
writer.flush(3).await?;
writer.finalize().await?;
Ok(())
}
#[tokio::test]
async fn multiple_flushes() -> anyhow::Result<()> {
let (response_tx, response_rx) = mpsc::channel(10);
let mut mock = MockBigQueryWrite::new();
mock.expect_append_rows()
.return_once(|_| Ok(TonicResponse::from(response_rx)));
mock.expect_flush_rows().times(2).returning(|req| {
Ok(TonicResponse::new(
bigquery_grpc_mock::google::cloud::bigquery::storage::v1::FlushRowsResponse {
offset: req.get_ref().offset.unwrap_or(0),
},
))
});
let (endpoint, _server) = start("0.0.0.0:0", mock).await?;
let transport = Arc::new(test_transport(endpoint).await?);
let writer = BufferedWriter::new(transport, write_stream(), format());
assert_eq!(writer.write_stream(), write_stream());
response_tx.send(Ok(convert(&test_response(1)))).await?;
let _ = writer.append(rows(1)).send().await?;
let flush1 = writer.flush(1).await?;
assert_eq!(flush1.offset, 1);
response_tx.send(Ok(convert(&test_response(2)))).await?;
let _ = writer.append(rows(2)).send().await?;
let flush2 = writer.flush(2).await?;
assert_eq!(flush2.offset, 2);
Ok(())
}
}