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}