1use crate::tds::stream::ReceivedToken;
2use crate::{row::ColumnType, Column, Row};
3use crate::{ColumnData, CommandResult, ResultMetadata};
4use futures_util::{
5 ready,
6 stream::{BoxStream, Peekable, Stream, StreamExt, TryStreamExt},
7};
8use std::{
9 fmt::Debug,
10 pin::Pin,
11 sync::Arc,
12 task::{self, Poll},
13};
14
15pub struct CommandStream<'a> {
83 token_stream: Peekable<BoxStream<'a, crate::Result<ReceivedToken>>>,
84 columns: Option<Arc<Vec<Column>>>,
85 result_set_index: Option<usize>,
86}
87
88impl<'a> Debug for CommandStream<'a> {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.debug_struct("CommandStream")
91 .field(
92 "token_stream",
93 &"BoxStream<'a, crate::Result<ReceivedToken>>",
94 )
95 .finish()
96 }
97}
98
99impl<'a> CommandStream<'a> {
100 pub(crate) fn new(token_stream: BoxStream<'a, crate::Result<ReceivedToken>>) -> Self {
101 Self {
102 token_stream: token_stream.peekable(),
103 columns: None,
104 result_set_index: None,
105 }
106 }
107
108 pub async fn into_command_result(mut self) -> crate::Result<CommandResult> {
111 let mut results: Vec<Vec<Row>> = Vec::new();
112 let mut result: Option<Vec<Row>> = None;
113 let mut return_status = 0;
114 let mut return_values = Vec::new();
115 let mut rows_affected = Vec::new();
116
117 while let Some(item) = self.try_next().await? {
118 match item {
119 CommandItem::Metadata(_) => results.extend(result.replace(Vec::new())),
122 CommandItem::Row(row) => result.get_or_insert_with(Vec::new).push(row),
123 CommandItem::ReturnStatus(rs) => return_status = rs,
124 CommandItem::ReturnValue(rv) => return_values.push(rv),
125 CommandItem::RowsAffected(rows) => rows_affected.push(rows),
126 }
127 }
128
129 if let Some(result) = result {
130 results.push(result);
131 }
132
133 Ok(CommandResult {
134 return_code: return_status,
135 return_values,
136 query_results: results,
137 rows_affected,
138 })
139 }
140
141 pub fn into_row_stream(self) -> BoxStream<'a, crate::Result<Row>> {
143 let s = self.try_filter_map(|item| async {
144 match item {
145 CommandItem::Row(row) => Ok(Some(row)),
146 _ => Ok(None),
147 }
148 });
149
150 Box::pin(s)
151 }
152}
153
154#[derive(Debug)]
158pub struct CommandReturnValue {
159 pub(crate) name: String,
160 pub(crate) ord: u16,
161 pub(crate) data: ColumnData<'static>,
162}
163
164impl CommandReturnValue {
165 pub fn name(&self) -> &str {
167 &self.name
168 }
169
170 pub fn ordinal(&self) -> u16 {
172 self.ord
173 }
174
175 pub fn data(&self) -> &ColumnData<'static> {
177 &self.data
178 }
179}
180
181#[derive(Debug)]
183pub enum CommandItem {
184 Row(Row),
186 Metadata(ResultMetadata),
188 ReturnStatus(i32),
190 ReturnValue(CommandReturnValue),
192 RowsAffected(u64),
198}
199
200impl CommandItem {
201 pub(crate) fn metadata(columns: Arc<Vec<Column>>, result_index: usize) -> Self {
202 Self::Metadata(ResultMetadata {
203 columns,
204 result_index,
205 })
206 }
207
208 pub fn as_metadata(&self) -> Option<&ResultMetadata> {
210 match self {
211 CommandItem::Metadata(ref metadata) => Some(metadata),
212 _ => None,
213 }
214 }
215
216 pub fn as_row(&self) -> Option<&Row> {
218 match self {
219 CommandItem::Row(ref row) => Some(row),
220 _ => None,
221 }
222 }
223
224 pub fn into_metadata(self) -> Option<ResultMetadata> {
226 match self {
227 CommandItem::Metadata(metadata) => Some(metadata),
228 _ => None,
229 }
230 }
231
232 pub fn into_row(self) -> Option<Row> {
234 match self {
235 CommandItem::Row(row) => Some(row),
236 _ => None,
237 }
238 }
239
240 pub fn as_return_status(&self) -> Option<i32> {
242 match self {
243 CommandItem::ReturnStatus(rs) => Some(*rs),
244 _ => None,
245 }
246 }
247
248 pub fn as_return_value(&self) -> Option<&CommandReturnValue> {
250 match self {
251 CommandItem::ReturnValue(rv) => Some(rv),
252 _ => None,
253 }
254 }
255
256 pub fn into_return_value(self) -> Option<CommandReturnValue> {
258 match self {
259 CommandItem::ReturnValue(rv) => Some(rv),
260 _ => None,
261 }
262 }
263}
264
265impl<'a> Stream for CommandStream<'a> {
266 type Item = crate::Result<CommandItem>;
267
268 fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
269 let this = self.get_mut();
270
271 loop {
272 let token = match ready!(this.token_stream.poll_next_unpin(cx)) {
273 Some(res) => res?,
274 None => return Poll::Ready(None),
275 };
276
277 return match token {
278 ReceivedToken::NewResultset(meta) => {
279 let column_meta = meta
280 .columns
281 .iter()
282 .map(|x| Column {
283 name: x.col_name.to_string(),
284 column_type: ColumnType::from(&x.base.ty),
285 })
286 .collect::<Vec<_>>();
287
288 let column_meta = Arc::new(column_meta);
289 this.columns = Some(column_meta.clone());
290
291 this.result_set_index = this.result_set_index.map(|i| i + 1);
292
293 let query_item =
294 CommandItem::metadata(column_meta, *this.result_set_index.get_or_insert(0));
295
296 Poll::Ready(Some(Ok(query_item)))
297 }
298 ReceivedToken::Row(data) => {
299 let Some(columns) = this.columns.as_ref() else {
300 return Poll::Ready(Some(Err(crate::Error::Protocol(
301 "ROW token arrived before any column metadata".into(),
302 ))));
303 };
304 let columns = columns.clone();
305 let result_index = this.result_set_index.unwrap_or(0);
306
307 let row = Row {
308 columns,
309 data,
310 result_index,
311 };
312
313 Poll::Ready(Some(Ok(CommandItem::Row(row))))
314 }
315 ReceivedToken::ReturnStatus(rs) => {
316 Poll::Ready(Some(Ok(CommandItem::ReturnStatus(rs))))
317 }
318 ReceivedToken::ReturnValue(rv) => {
319 Poll::Ready(Some(Ok(CommandItem::ReturnValue(CommandReturnValue {
320 name: rv.param_name,
321 ord: rv.param_ordinal,
322 data: rv.value,
323 }))))
324 }
325 token => match token.records_affected() {
326 Some(rows) => Poll::Ready(Some(Ok(CommandItem::RowsAffected(rows)))),
327 None => continue,
328 },
329 };
330 }
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use crate::tds::codec::{DoneStatus, TokenColMetaData, TokenDone, TokenRow};
338 use enumflags2::BitFlags;
339 use futures_util::stream;
340
341 const INSERT: u16 = 0xC3;
342 const UPDATE: u16 = 0xC5;
343 const SELECT: u16 = 0xC1;
344
345 fn meta() -> ReceivedToken {
346 ReceivedToken::NewResultset(Arc::new(TokenColMetaData { columns: vec![] }))
347 }
348
349 fn row() -> ReceivedToken {
350 ReceivedToken::Row(TokenRow::new())
351 }
352
353 fn count(cur_cmd: u16, rows: u64) -> TokenDone {
354 TokenDone::new(DoneStatus::More | DoneStatus::Count, cur_cmd, rows)
355 }
356
357 async fn collect(tokens: Vec<ReceivedToken>) -> CommandResult {
358 let tokens = stream::iter(tokens.into_iter().map(Ok)).boxed();
359 CommandStream::new(tokens)
360 .into_command_result()
361 .await
362 .unwrap()
363 }
364
365 fn set_sizes(res: &CommandResult) -> Vec<usize> {
366 res.query_results.iter().map(Vec::len).collect()
367 }
368
369 #[tokio::test]
370 async fn empty_result_sets_keep_their_index() {
371 let res = collect(vec![
372 meta(),
373 row(),
374 ReceivedToken::Done(count(SELECT, 1)),
375 meta(),
376 ReceivedToken::Done(count(SELECT, 0)),
377 meta(),
378 ReceivedToken::Done(count(SELECT, 0)),
379 meta(),
380 row(),
381 row(),
382 ReceivedToken::Done(count(SELECT, 2)),
383 ])
384 .await;
385 assert_eq!(set_sizes(&res), [1, 0, 0, 2]);
386 }
387
388 #[tokio::test]
389 async fn leading_empty_result_sets_are_kept() {
390 let res = collect(vec![
391 meta(),
392 ReceivedToken::Done(count(SELECT, 0)),
393 meta(),
394 ReceivedToken::Done(count(SELECT, 0)),
395 meta(),
396 row(),
397 ReceivedToken::Done(count(SELECT, 1)),
398 ])
399 .await;
400 assert_eq!(set_sizes(&res), [0, 0, 1]);
401 }
402
403 #[tokio::test]
404 async fn rows_affected_skips_selects_and_uncounted_dones() {
405 let res = collect(vec![
406 ReceivedToken::DoneInProc(count(INSERT, 2)),
407 meta(),
408 row(),
409 ReceivedToken::DoneInProc(count(SELECT, 1)),
410 ReceivedToken::DoneInProc(count(SELECT, 1)),
412 ReceivedToken::DoneInProc(count(UPDATE, 0)),
413 ReceivedToken::ReturnStatus(-1),
414 ReceivedToken::DoneProc(TokenDone::new(BitFlags::empty(), 0xE0, 0)),
415 ])
416 .await;
417 assert_eq!(res.rows_affected(), [2, 0]);
418 assert_eq!(res.return_code(), -1);
419 assert_eq!(set_sizes(&res), [1]);
420 }
421}