use crate::tds::stream::ReceivedToken;
use crate::{row::ColumnType, Column, Row};
use crate::{ColumnData, CommandResult, ResultMetadata};
use futures_util::{
ready,
stream::{BoxStream, Peekable, Stream, StreamExt, TryStreamExt},
};
use std::{
fmt::Debug,
pin::Pin,
sync::Arc,
task::{self, Poll},
};
pub struct CommandStream<'a> {
token_stream: Peekable<BoxStream<'a, crate::Result<ReceivedToken>>>,
columns: Option<Arc<Vec<Column>>>,
result_set_index: Option<usize>,
}
impl<'a> Debug for CommandStream<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandStream")
.field(
"token_stream",
&"BoxStream<'a, crate::Result<ReceivedToken>>",
)
.finish()
}
}
impl<'a> CommandStream<'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 async fn into_command_result(mut self) -> crate::Result<CommandResult> {
let mut results: Vec<Vec<Row>> = Vec::new();
let mut result: Option<Vec<Row>> = None;
let mut return_status = 0;
let mut return_values = Vec::new();
let mut rows_affected = Vec::new();
while let Some(item) = self.try_next().await? {
match item {
CommandItem::Metadata(_) => results.extend(result.replace(Vec::new())),
CommandItem::Row(row) => result.get_or_insert_with(Vec::new).push(row),
CommandItem::ReturnStatus(rs) => return_status = rs,
CommandItem::ReturnValue(rv) => return_values.push(rv),
CommandItem::RowsAffected(rows) => rows_affected.push(rows),
}
}
if let Some(result) = result {
results.push(result);
}
Ok(CommandResult {
return_code: return_status,
return_values,
query_results: results,
rows_affected,
})
}
pub fn into_row_stream(self) -> BoxStream<'a, crate::Result<Row>> {
let s = self.try_filter_map(|item| async {
match item {
CommandItem::Row(row) => Ok(Some(row)),
_ => Ok(None),
}
});
Box::pin(s)
}
}
#[derive(Debug)]
pub struct CommandReturnValue {
pub(crate) name: String,
pub(crate) ord: u16,
pub(crate) data: ColumnData<'static>,
}
impl CommandReturnValue {
pub fn name(&self) -> &str {
&self.name
}
pub fn ordinal(&self) -> u16 {
self.ord
}
pub fn data(&self) -> &ColumnData<'static> {
&self.data
}
}
#[derive(Debug)]
pub enum CommandItem {
Row(Row),
Metadata(ResultMetadata),
ReturnStatus(i32),
ReturnValue(CommandReturnValue),
RowsAffected(u64),
}
impl CommandItem {
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 {
CommandItem::Metadata(ref metadata) => Some(metadata),
_ => None,
}
}
pub fn as_row(&self) -> Option<&Row> {
match self {
CommandItem::Row(ref row) => Some(row),
_ => None,
}
}
pub fn into_metadata(self) -> Option<ResultMetadata> {
match self {
CommandItem::Metadata(metadata) => Some(metadata),
_ => None,
}
}
pub fn into_row(self) -> Option<Row> {
match self {
CommandItem::Row(row) => Some(row),
_ => None,
}
}
pub fn as_return_status(&self) -> Option<i32> {
match self {
CommandItem::ReturnStatus(rs) => Some(*rs),
_ => None,
}
}
pub fn as_return_value(&self) -> Option<&CommandReturnValue> {
match self {
CommandItem::ReturnValue(rv) => Some(rv),
_ => None,
}
}
pub fn into_return_value(self) -> Option<CommandReturnValue> {
match self {
CommandItem::ReturnValue(rv) => Some(rv),
_ => None,
}
}
}
impl<'a> Stream for CommandStream<'a> {
type Item = crate::Result<CommandItem>;
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 =
CommandItem::metadata(column_meta, *this.result_set_index.get_or_insert(0));
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(CommandItem::Row(row))))
}
ReceivedToken::ReturnStatus(rs) => {
Poll::Ready(Some(Ok(CommandItem::ReturnStatus(rs))))
}
ReceivedToken::ReturnValue(rv) => {
Poll::Ready(Some(Ok(CommandItem::ReturnValue(CommandReturnValue {
name: rv.param_name,
ord: rv.param_ordinal,
data: rv.value,
}))))
}
token => match token.records_affected() {
Some(rows) => Poll::Ready(Some(Ok(CommandItem::RowsAffected(rows)))),
None => continue,
},
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tds::codec::{DoneStatus, TokenColMetaData, TokenDone, TokenRow};
use enumflags2::BitFlags;
use futures_util::stream;
const INSERT: u16 = 0xC3;
const UPDATE: u16 = 0xC5;
const SELECT: u16 = 0xC1;
fn meta() -> ReceivedToken {
ReceivedToken::NewResultset(Arc::new(TokenColMetaData { columns: vec![] }))
}
fn row() -> ReceivedToken {
ReceivedToken::Row(TokenRow::new())
}
fn count(cur_cmd: u16, rows: u64) -> TokenDone {
TokenDone::new(DoneStatus::More | DoneStatus::Count, cur_cmd, rows)
}
async fn collect(tokens: Vec<ReceivedToken>) -> CommandResult {
let tokens = stream::iter(tokens.into_iter().map(Ok)).boxed();
CommandStream::new(tokens)
.into_command_result()
.await
.unwrap()
}
fn set_sizes(res: &CommandResult) -> Vec<usize> {
res.query_results.iter().map(Vec::len).collect()
}
#[tokio::test]
async fn empty_result_sets_keep_their_index() {
let res = collect(vec![
meta(),
row(),
ReceivedToken::Done(count(SELECT, 1)),
meta(),
ReceivedToken::Done(count(SELECT, 0)),
meta(),
ReceivedToken::Done(count(SELECT, 0)),
meta(),
row(),
row(),
ReceivedToken::Done(count(SELECT, 2)),
])
.await;
assert_eq!(set_sizes(&res), [1, 0, 0, 2]);
}
#[tokio::test]
async fn leading_empty_result_sets_are_kept() {
let res = collect(vec![
meta(),
ReceivedToken::Done(count(SELECT, 0)),
meta(),
ReceivedToken::Done(count(SELECT, 0)),
meta(),
row(),
ReceivedToken::Done(count(SELECT, 1)),
])
.await;
assert_eq!(set_sizes(&res), [0, 0, 1]);
}
#[tokio::test]
async fn rows_affected_skips_selects_and_uncounted_dones() {
let res = collect(vec![
ReceivedToken::DoneInProc(count(INSERT, 2)),
meta(),
row(),
ReceivedToken::DoneInProc(count(SELECT, 1)),
ReceivedToken::DoneInProc(count(SELECT, 1)),
ReceivedToken::DoneInProc(count(UPDATE, 0)),
ReceivedToken::ReturnStatus(-1),
ReceivedToken::DoneProc(TokenDone::new(BitFlags::empty(), 0xE0, 0)),
])
.await;
assert_eq!(res.rows_affected(), [2, 0]);
assert_eq!(res.return_code(), -1);
assert_eq!(set_sizes(&res), [1]);
}
}