use crate::tds::stream::ReceivedToken;
use crate::{row::ColumnType, Column, Row};
use futures_util::{
ready,
stream::{BoxStream, Peekable, Stream, StreamExt, TryStreamExt},
};
use std::{
fmt::Debug,
pin::Pin,
sync::Arc,
task::{self, Poll},
};
pub struct QueryStream<'a> {
token_stream: Peekable<BoxStream<'a, crate::Result<ReceivedToken>>>,
columns: Option<Arc<Vec<Column>>>,
result_set_index: Option<usize>,
}
impl<'a> Debug for QueryStream<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QueryStream")
.field(
"token_stream",
&"BoxStream<'a, crate::Result<ReceivedToken>>",
)
.finish()
}
}
impl<'a> QueryStream<'a> {
pub(crate) fn new(token_stream: BoxStream<'a, crate::Result<ReceivedToken>>) -> Self {
Self {
token_stream: token_stream.peekable(),
columns: None,
result_set_index: None,
}
}
pub(crate) async fn forward_to_metadata(&mut self) -> crate::Result<()> {
loop {
let item = Pin::new(&mut self.token_stream)
.peek()
.await
.map(|r| r.as_ref().map_err(|e| e.clone()))
.transpose()?;
match item {
Some(ReceivedToken::NewResultset(_)) => break,
Some(_) => {
self.token_stream.try_next().await?;
}
None => break,
}
}
Ok(())
}
pub async fn columns(&mut self) -> crate::Result<Option<&[Column]>> {
use ReceivedToken::*;
loop {
let item = Pin::new(&mut self.token_stream)
.peek()
.await
.map(|r| r.as_ref().map_err(|e| e.clone()))
.transpose()?;
match item {
Some(token) => match token {
NewResultset(metadata) => {
self.columns = Some(Arc::new(metadata.columns().collect()));
break;
}
Row(_) => {
break;
}
_ => {
self.token_stream.try_next().await?;
continue;
}
},
None => {
break;
}
}
}
Ok(self.columns.as_ref().map(|c| c.as_slice()))
}
pub async fn into_results(self) -> crate::Result<Vec<Vec<Row>>> {
collect_results(self).await
}
pub async fn into_first_result(self) -> crate::Result<Vec<Row>> {
let mut results = self.into_results().await?.into_iter();
let rows = results.next().unwrap_or_default();
Ok(rows)
}
pub async fn into_row(self) -> crate::Result<Option<Row>> {
let mut results = self.into_first_result().await?.into_iter();
Ok(results.next())
}
pub fn into_row_stream(self) -> BoxStream<'a, crate::Result<Row>> {
let s = self.try_filter_map(|item| async {
match item {
QueryItem::Row(row) => Ok(Some(row)),
QueryItem::Metadata(_) => Ok(None),
}
});
Box::pin(s)
}
}
async fn collect_results<S>(mut stream: S) -> crate::Result<Vec<Vec<Row>>>
where
S: Stream<Item = crate::Result<QueryItem>> + Unpin,
{
let mut results: Vec<Vec<Row>> = Vec::new();
let mut current: Option<Vec<Row>> = None;
while let Some(item) = stream.try_next().await? {
match item {
QueryItem::Metadata(_) => {
if let Some(previous) = current.take() {
results.push(previous);
}
current = Some(Vec::new());
}
QueryItem::Row(row) => {
current.get_or_insert_with(Vec::new).push(row);
}
}
}
if let Some(last) = current.take() {
results.push(last);
}
Ok(results)
}
#[derive(Debug, Clone)]
pub struct ResultMetadata {
pub(crate) columns: Arc<Vec<Column>>,
pub(crate) result_index: usize,
}
impl ResultMetadata {
pub fn columns(&self) -> &[Column] {
&self.columns
}
pub fn result_index(&self) -> usize {
self.result_index
}
}
#[derive(Debug)]
pub enum QueryItem {
Row(Row),
Metadata(ResultMetadata),
}
impl QueryItem {
pub(crate) fn metadata(columns: Arc<Vec<Column>>, result_index: usize) -> Self {
Self::Metadata(ResultMetadata {
columns,
result_index,
})
}
pub fn as_metadata(&self) -> Option<&ResultMetadata> {
match self {
QueryItem::Row(_) => None,
QueryItem::Metadata(ref metadata) => Some(metadata),
}
}
pub fn as_row(&self) -> Option<&Row> {
match self {
QueryItem::Row(ref row) => Some(row),
QueryItem::Metadata(_) => None,
}
}
pub fn into_metadata(self) -> Option<ResultMetadata> {
match self {
QueryItem::Row(_) => None,
QueryItem::Metadata(metadata) => Some(metadata),
}
}
pub fn into_row(self) -> Option<Row> {
match self {
QueryItem::Row(row) => Some(row),
QueryItem::Metadata(_) => None,
}
}
}
impl<'a> Stream for QueryStream<'a> {
type Item = crate::Result<QueryItem>;
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
let token = match ready!(this.token_stream.poll_next_unpin(cx)) {
Some(res) => res?,
None => return Poll::Ready(None),
};
return match token {
ReceivedToken::NewResultset(meta) => {
let column_meta = meta
.columns
.iter()
.map(|x| Column {
name: x.col_name.to_string(),
column_type: ColumnType::from(&x.base.ty),
})
.collect::<Vec<_>>();
let column_meta = Arc::new(column_meta);
this.columns = Some(column_meta.clone());
this.result_set_index = this.result_set_index.map(|i| i + 1);
let query_item =
QueryItem::metadata(column_meta, *this.result_set_index.get_or_insert(0));
return Poll::Ready(Some(Ok(query_item)));
}
ReceivedToken::Row(data) => {
let Some(columns) = this.columns.as_ref() else {
return Poll::Ready(Some(Err(crate::Error::Protocol(
"ROW token arrived before any column metadata".into(),
))));
};
let columns = columns.clone();
let result_index = this.result_set_index.unwrap_or(0);
let row = Row {
columns,
data,
result_index,
};
Poll::Ready(Some(Ok(QueryItem::Row(row))))
}
_ => continue,
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::row::ColumnType;
use crate::tds::codec::TokenRow;
use crate::Column;
use futures_util::stream;
fn columns() -> Arc<Vec<Column>> {
Arc::new(vec![Column::new("c".to_string(), ColumnType::Int4)])
}
fn meta(cols: &Arc<Vec<Column>>, result_index: usize) -> QueryItem {
QueryItem::metadata(cols.clone(), result_index)
}
fn row(cols: &Arc<Vec<Column>>, result_index: usize) -> QueryItem {
QueryItem::Row(Row {
columns: cols.clone(),
data: TokenRow::new(),
result_index,
})
}
async fn collect(items: Vec<QueryItem>) -> Vec<Vec<Row>> {
let stream = stream::iter(items.into_iter().map(Ok::<_, crate::error::Error>));
collect_results(stream).await.expect("collect_results")
}
#[tokio::test]
async fn empty_stream_yields_no_result_sets() {
assert!(collect(vec![]).await.is_empty());
}
#[tokio::test]
async fn metadata_without_rows_yields_one_empty_result_set() {
let cols = columns();
let results = collect(vec![meta(&cols, 0)]).await;
assert_eq!(results.len(), 1);
assert!(results[0].is_empty());
}
#[tokio::test]
async fn multiple_empty_result_sets_are_preserved() {
let cols = columns();
let results = collect(vec![meta(&cols, 0), meta(&cols, 1)]).await;
assert_eq!(results.len(), 2);
assert!(results[0].is_empty());
assert!(results[1].is_empty());
}
#[tokio::test]
async fn rows_are_grouped_by_result_set_in_order() {
let cols = columns();
let results = collect(vec![
meta(&cols, 0),
row(&cols, 0),
row(&cols, 0),
meta(&cols, 1),
row(&cols, 1),
])
.await;
assert_eq!(results.len(), 2);
assert_eq!(results[0].len(), 2);
assert_eq!(results[1].len(), 1);
}
#[tokio::test]
async fn leading_row_is_not_discarded() {
let cols = columns();
let results = collect(vec![row(&cols, 0), row(&cols, 0)]).await;
assert_eq!(results.len(), 1);
assert_eq!(results[0].len(), 2);
}
#[tokio::test]
async fn single_metadata_with_rows_yields_one_result_set_of_n_rows() {
let cols = columns();
let results = collect(vec![
meta(&cols, 0),
row(&cols, 0),
row(&cols, 0),
row(&cols, 0),
])
.await;
assert_eq!(results.len(), 1);
assert_eq!(results[0].len(), 3);
}
#[tokio::test]
async fn collect_propagates_errors() {
let cols = columns();
let items = vec![
Ok(meta(&cols, 0)),
Ok(row(&cols, 0)),
Err(crate::Error::Protocol("boom".into())),
];
let stream = stream::iter(items);
let err = collect_results(stream).await.expect_err("expected error");
assert!(matches!(err, crate::Error::Protocol(_)));
}
#[test]
fn query_item_accessors_on_metadata() {
let cols = columns();
let item = meta(&cols, 7);
assert!(item.as_metadata().is_some());
assert!(item.as_row().is_none());
assert_eq!(item.as_metadata().unwrap().result_index(), 7);
let md = item.into_metadata().expect("metadata");
assert_eq!(md.result_index(), 7);
assert_eq!(md.columns().len(), 1);
}
#[test]
fn query_item_accessors_on_row() {
let cols = columns();
let item = row(&cols, 3);
assert!(item.as_row().is_some());
assert!(item.as_metadata().is_none());
assert_eq!(item.as_row().unwrap().result_index, 3);
let r = item.into_row().expect("row");
assert_eq!(r.result_index, 3);
}
#[test]
fn query_item_into_wrong_variant_is_none() {
let cols = columns();
assert!(meta(&cols, 0).into_row().is_none());
assert!(row(&cols, 0).into_metadata().is_none());
}
use crate::tds::codec::{
BaseMetaDataColumn, FixedLenType, MetaDataColumn, TokenColMetaData, TypeInfo,
};
use crate::tds::stream::ReceivedToken;
use futures_util::stream::{StreamExt, TryStreamExt};
use std::borrow::Cow;
fn token_meta(name: &'static str) -> ReceivedToken {
let col = MetaDataColumn {
base: BaseMetaDataColumn {
flags: enumflags2::BitFlags::empty(),
ty: TypeInfo::FixedLen(FixedLenType::Int4),
table_name: None,
},
col_name: Cow::Borrowed(name),
};
ReceivedToken::NewResultset(Arc::new(TokenColMetaData { columns: vec![col] }))
}
fn token_row() -> ReceivedToken {
ReceivedToken::Row(TokenRow::new())
}
fn query_stream(tokens: Vec<ReceivedToken>) -> QueryStream<'static> {
let s = stream::iter(tokens.into_iter().map(Ok::<_, crate::Error>));
QueryStream::new(s.boxed())
}
#[tokio::test]
async fn query_stream_into_results_groups_and_indexes_result_sets() {
let stream = query_stream(vec![
token_meta("first"),
token_row(),
token_row(),
token_meta("second"),
token_row(),
]);
let results = stream.into_results().await.expect("into_results");
assert_eq!(results.len(), 2);
assert_eq!(results[0].len(), 2);
assert_eq!(results[1].len(), 1);
assert!(results[0].iter().all(|r| r.result_index == 0));
assert!(results[1].iter().all(|r| r.result_index == 1));
}
#[tokio::test]
async fn query_stream_empty_yields_no_results() {
let results = query_stream(vec![]).into_results().await.expect("empty");
assert!(results.is_empty());
}
#[tokio::test]
async fn query_stream_metadata_only_yields_one_empty_result_set() {
let results = query_stream(vec![token_meta("first")])
.into_results()
.await
.expect("metadata only");
assert_eq!(results.len(), 1);
assert!(results[0].is_empty());
}
#[tokio::test]
async fn query_stream_row_before_metadata_is_protocol_error() {
let err = query_stream(vec![token_row()])
.into_results()
.await
.expect_err("expected protocol error");
assert!(matches!(err, crate::Error::Protocol(_)));
}
#[tokio::test]
async fn query_stream_into_first_result_and_into_row() {
let first = query_stream(vec![
token_meta("first"),
token_row(),
token_row(),
token_meta("second"),
token_row(),
])
.into_first_result()
.await
.expect("into_first_result");
assert_eq!(first.len(), 2);
let one = query_stream(vec![token_meta("first"), token_row(), token_row()])
.into_row()
.await
.expect("into_row");
assert!(one.is_some());
let none = query_stream(vec![token_meta("first")])
.into_row()
.await
.expect("into_row empty");
assert!(none.is_none());
}
#[tokio::test]
async fn query_stream_into_row_stream_skips_metadata() {
let rows: Vec<Row> = query_stream(vec![
token_meta("first"),
token_row(),
token_meta("second"),
token_row(),
token_row(),
])
.into_row_stream()
.try_collect()
.await
.expect("into_row_stream");
assert_eq!(rows.len(), 3);
}
}