Skip to main content

tiberius/tds/stream/
query.rs

1use crate::tds::stream::ReceivedToken;
2use crate::{row::ColumnType, Column, Row};
3use futures_util::{
4    ready,
5    stream::{BoxStream, Peekable, Stream, StreamExt, TryStreamExt},
6};
7use std::{
8    fmt::Debug,
9    pin::Pin,
10    sync::Arc,
11    task::{self, Poll},
12};
13
14/// A set of `Streams` of [`QueryItem`] values, which can be either result
15/// metadata or a row.
16///
17/// The `QueryStream` needs to be polled empty before sending another query to
18/// the [`Client`], failing to do so causes a flush before the next query,
19/// slowing it down in an undeterministic way.
20///
21/// Every stream starts with metadata, describing the structure of the incoming
22/// rows, e.g. the columns in the order they are presented in every row.
23///
24/// If after consuming rows from the stream, another metadata result arrives, it
25/// means the stream has multiple results from different queries. This new
26/// metadata item will describe the next rows from here forwards.
27///
28/// If having one set of results in the response, using [`into_row_stream`]
29/// might be more convenient to use.
30///
31/// The struct provides non-streaming APIs with [`into_results`],
32/// [`into_first_result`] and [`into_row`].
33///
34/// # Example
35///
36/// ```
37/// # use tiberius::{Config, QueryItem};
38/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
39/// # use std::env;
40/// # use futures_util::stream::TryStreamExt;
41/// # #[tokio::main]
42/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
43/// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
44/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
45/// # );
46/// # let config = Config::from_ado_string(&c_str)?;
47/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
48/// # tcp.set_nodelay(true)?;
49/// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
50/// let mut stream = client
51///     .query(
52///         "SELECT @P1 AS first; SELECT @P2 AS second",
53///         &[&1i32, &2i32],
54///     )
55///     .await?;
56///
57/// // The stream consists of four items, in the following order:
58/// // - Metadata from `SELECT 1`
59/// // - The only resulting row from `SELECT 1`
60/// // - Metadata from `SELECT 2`
61/// // - The only resulting row from `SELECT 2`
62/// while let Some(item) = stream.try_next().await? {
63///     match item {
64///         // our first item is the column data always
65///         QueryItem::Metadata(meta) if meta.result_index() == 0 => {
66///             // the first result column info can be handled here
67///         }
68///         // ... and from there on from 0..N rows
69///         QueryItem::Row(row) if row.result_index() == 0 => {
70///             assert_eq!(Some(1), row.get(0));
71///         }
72///         // the second result set returns first another metadata item
73///         QueryItem::Metadata(meta) => {
74///             // .. handling
75///         }
76///         // ...and, again, we get rows from the second resultset
77///         QueryItem::Row(row) => {
78///             assert_eq!(Some(2), row.get(0));
79///         }
80///     }
81/// }
82/// # Ok(())
83/// # }
84/// ```
85///
86/// [`Client`]: struct.Client.html
87/// [`into_row_stream`]: struct.QueryStream.html#method.into_row_stream
88/// [`into_results`]: struct.QueryStream.html#method.into_results
89/// [`into_first_result`]: struct.QueryStream.html#method.into_first_result
90/// [`into_row`]: struct.QueryStream.html#method.into_row
91pub struct QueryStream<'a> {
92    token_stream: Peekable<BoxStream<'a, crate::Result<ReceivedToken>>>,
93    columns: Option<Arc<Vec<Column>>>,
94    result_set_index: Option<usize>,
95}
96
97impl<'a> Debug for QueryStream<'a> {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        f.debug_struct("QueryStream")
100            .field(
101                "token_stream",
102                &"BoxStream<'a, crate::Result<ReceivedToken>>",
103            )
104            .finish()
105    }
106}
107
108impl<'a> QueryStream<'a> {
109    pub(crate) fn new(token_stream: BoxStream<'a, crate::Result<ReceivedToken>>) -> Self {
110        Self {
111            token_stream: token_stream.peekable(),
112            columns: None,
113            result_set_index: None,
114        }
115    }
116
117    /// Moves the stream forward until having result metadata, stream end or an
118    /// error.
119    pub(crate) async fn forward_to_metadata(&mut self) -> crate::Result<()> {
120        loop {
121            let item = Pin::new(&mut self.token_stream)
122                .peek()
123                .await
124                .map(|r| r.as_ref().map_err(|e| e.clone()))
125                .transpose()?;
126
127            match item {
128                Some(ReceivedToken::NewResultset(_)) => break,
129                Some(_) => {
130                    self.token_stream.try_next().await?;
131                }
132                None => break,
133            }
134        }
135
136        Ok(())
137    }
138
139    /// The list of columns either for the current result set, or for the next
140    /// one. If the stream is just created, or if the next item in the stream
141    /// contains metadata, the metadata will be taken from the stream. Otherwise
142    /// the columns will be returned from the cache and reflect on the current
143    /// result set.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// # use tiberius::Config;
149    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
150    /// # use std::env;
151    /// # use futures_util::stream::TryStreamExt;
152    /// # #[tokio::main]
153    /// # async fn main() -> anyhow::Result<()> {
154    /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
155    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
156    /// # );
157    /// # let config = Config::from_ado_string(&c_str)?;
158    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
159    /// # tcp.set_nodelay(true)?;
160    /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
161    /// let mut stream = client
162    ///     .query(
163    ///         "SELECT @P1 AS first; SELECT @P2 AS second",
164    ///         &[&1i32, &2i32],
165    ///     )
166    ///     .await?;
167    ///
168    /// // Nothing is fetched, the first result set starts.
169    /// let cols = stream.columns().await?.unwrap();
170    /// assert_eq!("first", cols[0].name());
171    ///
172    /// // Move over the metadata.
173    /// stream.try_next().await?;
174    ///
175    /// // We're in the first row, seeing the metadata for that set.
176    /// let cols = stream.columns().await?.unwrap();
177    /// assert_eq!("first", cols[0].name());
178    ///
179    /// // Move over the only row in the first set.
180    /// stream.try_next().await?;
181    ///
182    /// // End of the first set, getting the metadata by peaking the next item.
183    /// let cols = stream.columns().await?.unwrap();
184    /// assert_eq!("second", cols[0].name());
185    /// # Ok(())
186    /// # }
187    /// ```
188    pub async fn columns(&mut self) -> crate::Result<Option<&[Column]>> {
189        use ReceivedToken::*;
190
191        loop {
192            let item = Pin::new(&mut self.token_stream)
193                .peek()
194                .await
195                .map(|r| r.as_ref().map_err(|e| e.clone()))
196                .transpose()?;
197
198            match item {
199                Some(token) => match token {
200                    NewResultset(metadata) => {
201                        self.columns = Some(Arc::new(metadata.columns().collect()));
202                        break;
203                    }
204                    Row(_) => {
205                        break;
206                    }
207                    _ => {
208                        self.token_stream.try_next().await?;
209                        continue;
210                    }
211                },
212                None => {
213                    break;
214                }
215            }
216        }
217
218        Ok(self.columns.as_ref().map(|c| c.as_slice()))
219    }
220
221    /// Collects results from all queries in the stream into memory in the order
222    /// of querying.
223    pub async fn into_results(mut self) -> crate::Result<Vec<Vec<Row>>> {
224        let mut results: Vec<Vec<Row>> = Vec::new();
225        let mut result: Vec<Row> = if self.try_next().await?.is_some() {
226            Vec::new()
227        } else {
228            return Ok(results);
229        };
230
231        while let Some(item) = self.try_next().await? {
232            if let QueryItem::Row(row) = item {
233                result.push(row);
234            } else {
235                results.push(result);
236                result = Vec::new();
237            }
238        }
239        results.push(result);
240        Ok(results)
241    }
242
243    /// Collects the output of the first query, dropping any further
244    /// results.
245    pub async fn into_first_result(self) -> crate::Result<Vec<Row>> {
246        let mut results = self.into_results().await?.into_iter();
247        let rows = results.next().unwrap_or_default();
248
249        Ok(rows)
250    }
251
252    /// Collects the first row from the output of the first query, dropping any
253    /// further rows.
254    pub async fn into_row(self) -> crate::Result<Option<Row>> {
255        let mut results = self.into_first_result().await?.into_iter();
256
257        Ok(results.next())
258    }
259
260    /// Convert the stream into a stream of rows, skipping metadata items.
261    pub fn into_row_stream(self) -> BoxStream<'a, crate::Result<Row>> {
262        let s = self.try_filter_map(|item| async {
263            match item {
264                QueryItem::Row(row) => Ok(Some(row)),
265                QueryItem::Metadata(_) => Ok(None),
266            }
267        });
268
269        Box::pin(s)
270    }
271}
272
273/// Info about the following stream of rows.
274#[derive(Debug, Clone)]
275pub struct ResultMetadata {
276    pub(crate) columns: Arc<Vec<Column>>,
277    pub(crate) result_index: usize,
278}
279
280impl ResultMetadata {
281    /// Column info. The order is the same as in the following rows.
282    pub fn columns(&self) -> &[Column] {
283        &self.columns
284    }
285
286    /// The number of the result set, an incrementing value starting from zero,
287    /// which gives an indication of the position of the result set in the
288    /// stream.
289    pub fn result_index(&self) -> usize {
290        self.result_index
291    }
292}
293
294/// Resulting data from a query.
295#[derive(Debug)]
296pub enum QueryItem {
297    /// A single row of data.
298    Row(Row),
299    /// Information of the upcoming row data.
300    Metadata(ResultMetadata),
301}
302
303impl QueryItem {
304    pub(crate) fn metadata(columns: Arc<Vec<Column>>, result_index: usize) -> Self {
305        Self::Metadata(ResultMetadata {
306            columns,
307            result_index,
308        })
309    }
310
311    /// Returns a reference to the metadata, if the item is of a correct variant.
312    pub fn as_metadata(&self) -> Option<&ResultMetadata> {
313        match self {
314            QueryItem::Row(_) => None,
315            QueryItem::Metadata(ref metadata) => Some(metadata),
316        }
317    }
318
319    /// Returns a reference to the row, if the item is of a correct variant.
320    pub fn as_row(&self) -> Option<&Row> {
321        match self {
322            QueryItem::Row(ref row) => Some(row),
323            QueryItem::Metadata(_) => None,
324        }
325    }
326
327    /// Returns the metadata, if the item is of a correct variant.
328    pub fn into_metadata(self) -> Option<ResultMetadata> {
329        match self {
330            QueryItem::Row(_) => None,
331            QueryItem::Metadata(metadata) => Some(metadata),
332        }
333    }
334
335    /// Returns the row, if the item is of a correct variant.
336    pub fn into_row(self) -> Option<Row> {
337        match self {
338            QueryItem::Row(row) => Some(row),
339            QueryItem::Metadata(_) => None,
340        }
341    }
342}
343
344impl<'a> Stream for QueryStream<'a> {
345    type Item = crate::Result<QueryItem>;
346
347    fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
348        let this = self.get_mut();
349
350        loop {
351            let token = match ready!(this.token_stream.poll_next_unpin(cx)) {
352                Some(res) => res?,
353                None => return Poll::Ready(None),
354            };
355
356            return match token {
357                ReceivedToken::NewResultset(meta) => {
358                    let column_meta = meta
359                        .columns
360                        .iter()
361                        .map(|x| Column {
362                            name: x.col_name.to_string(),
363                            column_type: ColumnType::from(&x.base.ty),
364                        })
365                        .collect::<Vec<_>>();
366
367                    let column_meta = Arc::new(column_meta);
368                    this.columns = Some(column_meta.clone());
369
370                    this.result_set_index = this.result_set_index.map(|i| i + 1);
371
372                    let query_item =
373                        QueryItem::metadata(column_meta, *this.result_set_index.get_or_insert(0));
374
375                    return Poll::Ready(Some(Ok(query_item)));
376                }
377                ReceivedToken::Row(data) => {
378                    let Some(columns) = this.columns.as_ref() else {
379                        return Poll::Ready(Some(Err(crate::Error::Protocol(
380                            "ROW token arrived before any column metadata".into(),
381                        ))));
382                    };
383                    let columns = columns.clone();
384                    let result_index = this.result_set_index.unwrap_or(0);
385
386                    let row = Row {
387                        columns,
388                        data,
389                        result_index,
390                    };
391
392                    Poll::Ready(Some(Ok(QueryItem::Row(row))))
393                }
394                _ => continue,
395            };
396        }
397    }
398}