Skip to main content

tiberius/tds/stream/
command.rs

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
15/// A `Stream` of [`CommandItem`] values produced by executing a [`Command`].
16///
17/// Items can be result metadata, rows, a return status, return values (OUT
18/// parameters) or a rows-affected count.
19///
20/// [`Command`]: crate::Command
21///
22/// # Example
23///
24/// ```no_run
25/// # use std::env;
26/// # use tiberius::Config;
27/// # use tiberius::{Command, CommandItem};
28/// # use futures_util::TryStreamExt;
29/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
30/// # #[tokio::main]
31/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
32/// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
33/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
34/// # );
35/// # let config = Config::from_ado_string(&c_str)?;
36/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
37/// # tcp.set_nodelay(true)?;
38/// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
39/// let mut cmd = Command::new("dbo.usp_SomeStoredProc");
40///
41/// cmd.bind_param("@foo", 34i32);
42/// cmd.bind_param("@zoo", "the zoo string prm");
43/// cmd.bind_out_param("@bar", "bar");
44/// let mut stream = cmd.exec(&mut client).await?;
45///
46/// while let Some(item) = stream.try_next().await? {
47///     match item {
48///         // our first item is the column data always
49///         CommandItem::Metadata(meta) if meta.result_index() == 0 => {
50///             // the first result column info can be handled here
51///         }
52///         // ... and from there on from 0..N rows
53///         CommandItem::Row(row) if row.result_index() == 0 => {
54///             let var: Option<i32> = row.get(0);
55///         }
56///         // the second result set returns first another metadata item
57///         CommandItem::Metadata(meta) => {
58///             // .. handling
59///         }
60///         // ...and, again, we get rows from the second resultset
61///         CommandItem::Row(row) => {
62///             let var: Option<i32> = row.get(0);
63///         }
64///         // check return status (returned always)
65///         CommandItem::ReturnStatus(rs) => {
66///             // .... do something
67///         }
68///         // collect OUT parameter values
69///         CommandItem::ReturnValue(rv) => {
70///             // .... do something, like push to a collection
71///         }
72///         // get affected row count
73///         CommandItem::RowsAffected(ra) => {
74///             // .... do something, like push to a collection
75///         }
76///     }
77/// }
78/// # Ok(())
79/// # }
80/// ```
81///
82pub 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    /// Collects all results from the command into memory, in the order they
109    /// were produced by the server.
110    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, &mut result) {
119                (CommandItem::Row(row), None) => {
120                    result = Some(vec![row]);
121                }
122                (CommandItem::Row(row), Some(ref mut result)) => result.push(row),
123                (CommandItem::Metadata(_), None) => {
124                    result = Some(Vec::new());
125                }
126                (CommandItem::Metadata(_), ref mut previous_result) => {
127                    results.push(previous_result.take().unwrap());
128                    result = None;
129                }
130                (CommandItem::ReturnStatus(rs), _) => return_status = rs,
131                (CommandItem::ReturnValue(rv), _) => return_values.push(rv),
132                (CommandItem::RowsAffected(rows), _) => rows_affected.push(rows),
133            }
134        }
135
136        if let Some(result) = result {
137            results.push(result);
138        }
139
140        Ok(CommandResult {
141            return_code: return_status,
142            return_values,
143            query_results: results,
144            rows_affected,
145        })
146    }
147
148    /// Converts the stream into a stream of rows, dropping all other items.
149    pub fn into_row_stream(self) -> BoxStream<'a, crate::Result<Row>> {
150        let s = self.try_filter_map(|item| async {
151            match item {
152                CommandItem::Row(row) => Ok(Some(row)),
153                _ => Ok(None),
154            }
155        });
156
157        Box::pin(s)
158    }
159}
160
161/// A single OUT parameter value returned by a [`Command`].
162///
163/// [`Command`]: crate::Command
164#[derive(Debug)]
165pub struct CommandReturnValue {
166    pub(crate) name: String,
167    pub(crate) ord: u16,
168    pub(crate) data: ColumnData<'static>,
169}
170
171impl CommandReturnValue {
172    /// The name of the OUT parameter this value corresponds to.
173    pub fn name(&self) -> &str {
174        &self.name
175    }
176
177    /// The ordinal of the OUT parameter as returned by the server.
178    pub fn ordinal(&self) -> u16 {
179        self.ord
180    }
181
182    /// A reference to the raw column data of the returned value.
183    pub fn data(&self) -> &ColumnData<'static> {
184        &self.data
185    }
186}
187
188/// An item produced by a [`CommandStream`].
189#[derive(Debug)]
190pub enum CommandItem {
191    /// A single row of data.
192    Row(Row),
193    /// Metadata describing the upcoming rows.
194    Metadata(ResultMetadata),
195    /// The return status from the server.
196    ReturnStatus(u32),
197    /// A return value, matching an OUT parameter.
198    ReturnValue(CommandReturnValue),
199    /// The number of rows affected by one of the statements ran on the server.
200    RowsAffected(u64),
201}
202
203impl CommandItem {
204    pub(crate) fn metadata(columns: Arc<Vec<Column>>, result_index: usize) -> Self {
205        Self::Metadata(ResultMetadata {
206            columns,
207            result_index,
208        })
209    }
210
211    /// Returns a reference to the metadata, if the item is of a correct variant.
212    pub fn as_metadata(&self) -> Option<&ResultMetadata> {
213        match self {
214            CommandItem::Metadata(ref metadata) => Some(metadata),
215            _ => None,
216        }
217    }
218
219    /// Returns a reference to the row, if the item is of a correct variant.
220    pub fn as_row(&self) -> Option<&Row> {
221        match self {
222            CommandItem::Row(ref row) => Some(row),
223            _ => None,
224        }
225    }
226
227    /// Returns the metadata, if the item is of a correct variant.
228    pub fn into_metadata(self) -> Option<ResultMetadata> {
229        match self {
230            CommandItem::Metadata(metadata) => Some(metadata),
231            _ => None,
232        }
233    }
234
235    /// Returns the row, if the item is of a correct variant.
236    pub fn into_row(self) -> Option<Row> {
237        match self {
238            CommandItem::Row(row) => Some(row),
239            _ => None,
240        }
241    }
242
243    /// Returns the return status, if the item is of a correct variant.
244    pub fn as_return_status(&self) -> Option<u32> {
245        match self {
246            CommandItem::ReturnStatus(rs) => Some(*rs),
247            _ => None,
248        }
249    }
250
251    /// Returns a reference to the return value, if the item is of a correct variant.
252    pub fn as_return_value(&self) -> Option<&CommandReturnValue> {
253        match self {
254            CommandItem::ReturnValue(rv) => Some(rv),
255            _ => None,
256        }
257    }
258
259    /// Returns the return value, if the item is of a correct variant.
260    pub fn into_return_value(self) -> Option<CommandReturnValue> {
261        match self {
262            CommandItem::ReturnValue(rv) => Some(rv),
263            _ => None,
264        }
265    }
266}
267
268impl<'a> Stream for CommandStream<'a> {
269    type Item = crate::Result<CommandItem>;
270
271    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
272        let this = self.get_mut();
273
274        loop {
275            let token = match ready!(this.token_stream.poll_next_unpin(cx)) {
276                Some(res) => res?,
277                None => return Poll::Ready(None),
278            };
279
280            return match token {
281                ReceivedToken::NewResultset(meta) => {
282                    let column_meta = meta
283                        .columns
284                        .iter()
285                        .map(|x| Column {
286                            name: x.col_name.to_string(),
287                            column_type: ColumnType::from(&x.base.ty),
288                        })
289                        .collect::<Vec<_>>();
290
291                    let column_meta = Arc::new(column_meta);
292                    this.columns = Some(column_meta.clone());
293
294                    this.result_set_index = this.result_set_index.map(|i| i + 1);
295
296                    let query_item =
297                        CommandItem::metadata(column_meta, *this.result_set_index.get_or_insert(0));
298
299                    Poll::Ready(Some(Ok(query_item)))
300                }
301                ReceivedToken::Row(data) => {
302                    let Some(columns) = this.columns.as_ref() else {
303                        return Poll::Ready(Some(Err(crate::Error::Protocol(
304                            "ROW token arrived before any column metadata".into(),
305                        ))));
306                    };
307                    let columns = columns.clone();
308                    let result_index = this.result_set_index.unwrap_or(0);
309
310                    let row = Row {
311                        columns,
312                        data,
313                        result_index,
314                    };
315
316                    Poll::Ready(Some(Ok(CommandItem::Row(row))))
317                }
318                ReceivedToken::ReturnStatus(rs) => {
319                    Poll::Ready(Some(Ok(CommandItem::ReturnStatus(rs))))
320                }
321                ReceivedToken::ReturnValue(rv) => {
322                    Poll::Ready(Some(Ok(CommandItem::ReturnValue(CommandReturnValue {
323                        name: rv.param_name,
324                        ord: rv.param_ordinal,
325                        data: rv.value,
326                    }))))
327                }
328                ReceivedToken::DoneProc(done) if done.is_final() => continue,
329                ReceivedToken::DoneProc(done) => {
330                    Poll::Ready(Some(Ok(CommandItem::RowsAffected(done.rows()))))
331                }
332                ReceivedToken::DoneInProc(done) => {
333                    Poll::Ready(Some(Ok(CommandItem::RowsAffected(done.rows()))))
334                }
335                ReceivedToken::Done(done) => {
336                    Poll::Ready(Some(Ok(CommandItem::RowsAffected(done.rows()))))
337                }
338                _ => continue,
339            };
340        }
341    }
342}