Skip to main content

mssql_client/
stream.rs

1//! Query result sets with lazy per-row decoding.
2//!
3//! The full server response is buffered in memory first; rows are then
4//! *decoded* lazily as callers pull them. This is not incremental
5//! network streaming — see the next section for what that means for memory.
6//!
7//! ## Buffered vs True Streaming
8//!
9//! The underlying TDS response is reassembled into a single [`bytes::Bytes`]
10//! payload by [`mssql-codec`](mssql_codec). That payload is handed to the
11//! token parser which walks it once and enqueues each row's raw byte slice
12//! (a cheap, refcounted slice into the original [`bytes::Bytes`] per ADR-004)
13//! into the stream. Individual [`Row`]s are then decoded lazily when callers
14//! pull them — either via the [`Stream`]/[`Iterator`] impls or
15//! [`QueryStream::collect_all`].
16//!
17//! This "lazy decode" pattern keeps peak memory at roughly the size of the
18//! raw payload instead of payload + fully-typed `Vec<Row>`. Users who iterate
19//! and drop each [`Row`] see memory proportional to a single row at a time
20//! plus the shared raw payload. Users who `collect_all()` pay for the full
21//! `Vec<Row>` just like before.
22//!
23//! The same lazy-decode pattern applies to [`MultiResultStream`],
24//! [`ResultSet`], and [`ProcedureResult::result_sets`]: raw row bytes are
25//! stashed during response read and each [`Row`] is decoded when the caller
26//! pulls it. Because decoding can fail per row, [`ResultSet::next_row`]
27//! returns `Option<Result<Row, Error>>` rather than `Option<Row>` — callers
28//! observe decode errors at iteration time instead of at
29//! `call_procedure().await?` / `query_multiple().await?`.
30//!
31//! For truly large result sets that should not be buffered at all, use
32//! [`Client::query_stream`](crate::Client::query_stream), which reads packets
33//! from the network on demand (peak memory ~one row), or page with OFFSET/FETCH.
34
35use std::collections::VecDeque;
36use std::pin::Pin;
37use std::sync::Arc;
38use std::task::{Context, Poll};
39
40use futures_core::Stream;
41use tds_protocol::token::{ColMetaData, NbcRow, RawRow};
42
43use crate::error::Error;
44use crate::row::{Column, Row};
45
46/// A row that may be already decoded or still held as raw TDS bytes.
47///
48/// The lazy-parse query path enqueues raw rows (cheap `Bytes` slices into
49/// the original response payload) and decodes them on demand. The eager
50/// path used by tests and [`MultiResultStream::into_query_streams`] wraps
51/// already-decoded rows.
52#[derive(Debug, Clone)]
53pub(crate) enum PendingRow {
54    /// Already-decoded row (eager path — tests + `MultiResultStream` compat).
55    Parsed(Row),
56    /// Raw TDS row bytes, to be decoded on pull.
57    Raw(RawRow),
58    /// Null-bitmap-compressed row bytes, to be decoded on pull.
59    Nbc(NbcRow),
60}
61
62/// A result set from a query, yielding rows one at a time.
63///
64/// The complete server response is already buffered in memory by the time
65/// this is returned; each [`Row`] is *decoded* lazily as it is pulled, not
66/// fetched incrementally from the network. Peak memory is therefore roughly
67/// the size of the raw response payload regardless of how you iterate. For
68/// genuinely large result sets, use
69/// [`Client::query_stream`](crate::Client::query_stream) (incremental, peak
70/// memory ~one row) or page with `OFFSET`/`FETCH` in SQL rather than relying on
71/// this type to bound memory.
72///
73/// # Example
74///
75/// ```rust,no_run
76/// # use mssql_client::Row;
77/// # fn process_row(_: &Row) {}
78/// # async fn ex(client: &mut mssql_client::Client<mssql_client::Ready>) -> Result<(), mssql_client::Error> {
79/// let stream = client.query("SELECT * FROM large_table", &[]).await?;
80/// for row in stream {
81///     let row = row?;
82///     process_row(&row);
83/// }
84/// # Ok(())
85/// # }
86/// ```
87#[must_use = "streams must be consumed; dropping a stream discards remaining rows"]
88pub struct QueryStream<'a> {
89    /// Column metadata for the result set.
90    row_meta: Arc<crate::row::ColMetaData>,
91    /// Buffered rows (typed or raw) from the response.
92    rows: VecDeque<PendingRow>,
93    /// Protocol metadata needed to decode raw rows. `None` if every pending
94    /// row is already [`PendingRow::Parsed`].
95    meta: Option<ColMetaData>,
96    /// Pre-resolved column decryptor for Always Encrypted result sets.
97    ///
98    /// Wrapped in `Arc` so lazy result-set containers (`ResultSet`) can share
99    /// this state without duplicating the derived-key material.
100    #[cfg(feature = "always-encrypted")]
101    decryptor: Option<std::sync::Arc<crate::column_decryptor::ColumnDecryptor>>,
102    /// Whether the stream has completed.
103    finished: bool,
104    /// Lifetime tied to the connection.
105    _marker: std::marker::PhantomData<&'a ()>,
106}
107
108impl QueryStream<'_> {
109    /// Create a new query stream from already-decoded rows.
110    ///
111    /// This is the eager constructor used by unit tests. Production query
112    /// paths use [`QueryStream::from_raw`] to defer row decoding.
113    /// [`MultiResultStream::into_query_streams`] uses
114    /// [`ResultSet::into_query_stream`] to preserve pending-row state.
115    #[cfg(test)]
116    pub(crate) fn new(columns: Vec<Column>, rows: Vec<Row>) -> Self {
117        Self {
118            row_meta: Arc::new(crate::row::ColMetaData::new(columns)),
119            rows: rows.into_iter().map(PendingRow::Parsed).collect(),
120            meta: None,
121            #[cfg(feature = "always-encrypted")]
122            decryptor: None,
123            finished: false,
124            _marker: std::marker::PhantomData,
125        }
126    }
127
128    /// Create a query stream from raw row bytes and protocol metadata.
129    ///
130    /// Rows are decoded on demand as the stream is pulled. The `meta` must
131    /// describe every row in `pending`. If decryption is configured,
132    /// `decryptor` must cover the same column set.
133    pub(crate) fn from_raw(
134        columns: Vec<Column>,
135        pending: Vec<PendingRow>,
136        meta: ColMetaData,
137        #[cfg(feature = "always-encrypted")] decryptor: Option<
138            std::sync::Arc<crate::column_decryptor::ColumnDecryptor>,
139        >,
140    ) -> Self {
141        Self {
142            row_meta: Arc::new(crate::row::ColMetaData::new(columns)),
143            rows: pending.into(),
144            meta: Some(meta),
145            #[cfg(feature = "always-encrypted")]
146            decryptor,
147            finished: false,
148            _marker: std::marker::PhantomData,
149        }
150    }
151
152    /// Create an empty query stream (no results).
153    #[allow(dead_code)]
154    pub(crate) fn empty() -> Self {
155        Self {
156            row_meta: Arc::new(crate::row::ColMetaData::new(Vec::new())),
157            rows: VecDeque::new(),
158            meta: None,
159            #[cfg(feature = "always-encrypted")]
160            decryptor: None,
161            finished: true,
162            _marker: std::marker::PhantomData,
163        }
164    }
165
166    /// Get the column metadata for this result set.
167    #[must_use]
168    pub fn columns(&self) -> &[Column] {
169        &self.row_meta.columns
170    }
171
172    /// Check if the stream has finished.
173    #[must_use]
174    pub fn is_finished(&self) -> bool {
175        self.finished
176    }
177
178    /// Get the number of rows remaining in the buffer.
179    #[must_use]
180    pub fn rows_remaining(&self) -> usize {
181        self.rows.len()
182    }
183
184    /// Collect all remaining rows into a vector.
185    ///
186    /// This consumes the stream and loads all rows into memory. Each row is
187    /// decoded lazily here, so large raw payloads are freed as rows are
188    /// produced rather than held alongside the typed `Vec<Row>` throughout
189    /// the caller's query call.
190    ///
191    /// For very large result sets, consider iterating with the stream
192    /// instead.
193    pub async fn collect_all(mut self) -> Result<Vec<Row>, Error> {
194        let mut out = Vec::with_capacity(self.rows.len());
195        while let Some(pending) = self.rows.pop_front() {
196            out.push(self.decode(pending)?);
197        }
198        self.finished = true;
199        Ok(out)
200    }
201
202    /// Try to get the next row synchronously (without async).
203    ///
204    /// Returns `None` when no more rows are available or the next pending
205    /// row fails to decode. Use [`Iterator::next`] instead if you need to
206    /// observe decode errors.
207    pub fn try_next(&mut self) -> Option<Row> {
208        self.next().and_then(|r| r.ok())
209    }
210
211    /// Decode a pending row into a typed [`Row`].
212    fn decode(&self, pending: PendingRow) -> Result<Row, Error> {
213        match pending {
214            PendingRow::Parsed(row) => Ok(row),
215            PendingRow::Raw(raw) => {
216                let meta = self
217                    .meta
218                    .as_ref()
219                    .ok_or_else(|| Error::Protocol("row metadata missing for raw row".into()))?;
220                #[cfg(feature = "always-encrypted")]
221                if let Some(ref dec) = self.decryptor {
222                    return crate::column_parser::convert_raw_row_decrypted(
223                        &raw,
224                        meta,
225                        &self.row_meta,
226                        dec,
227                    );
228                }
229                crate::column_parser::convert_raw_row(&raw, meta, &self.row_meta)
230            }
231            PendingRow::Nbc(nbc) => {
232                let meta = self
233                    .meta
234                    .as_ref()
235                    .ok_or_else(|| Error::Protocol("row metadata missing for NBC row".into()))?;
236                #[cfg(feature = "always-encrypted")]
237                if let Some(ref dec) = self.decryptor {
238                    return crate::column_parser::convert_nbc_row_decrypted(
239                        &nbc,
240                        meta,
241                        &self.row_meta,
242                        dec,
243                    );
244                }
245                crate::column_parser::convert_nbc_row(&nbc, meta, &self.row_meta)
246            }
247        }
248    }
249}
250
251impl Stream for QueryStream<'_> {
252    type Item = Result<Row, Error>;
253
254    fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
255        let this = self.get_mut();
256
257        if this.finished {
258            return Poll::Ready(None);
259        }
260
261        match this.rows.pop_front() {
262            Some(pending) => Poll::Ready(Some(this.decode(pending))),
263            None => {
264                this.finished = true;
265                Poll::Ready(None)
266            }
267        }
268    }
269}
270
271impl ExactSizeIterator for QueryStream<'_> {}
272
273impl Iterator for QueryStream<'_> {
274    type Item = Result<Row, Error>;
275
276    fn next(&mut self) -> Option<Self::Item> {
277        if self.finished {
278            return None;
279        }
280
281        match self.rows.pop_front() {
282            Some(pending) => Some(self.decode(pending)),
283            None => {
284                self.finished = true;
285                None
286            }
287        }
288    }
289
290    fn size_hint(&self) -> (usize, Option<usize>) {
291        let remaining = self.rows.len();
292        (remaining, Some(remaining))
293    }
294}
295
296/// Result of a non-query execution.
297///
298/// Contains the number of affected rows and any output parameters.
299#[derive(Debug, Clone)]
300#[non_exhaustive]
301#[must_use]
302pub struct ExecuteResult {
303    /// Number of rows affected by the statement.
304    pub rows_affected: u64,
305    /// Output parameters from stored procedures.
306    pub output_params: Vec<OutputParam>,
307}
308
309/// An output parameter from a stored procedure call.
310#[derive(Debug, Clone)]
311#[non_exhaustive]
312pub struct OutputParam {
313    /// Parameter name.
314    pub name: String,
315    /// Parameter value.
316    pub value: mssql_types::SqlValue,
317}
318
319impl ExecuteResult {
320    /// Create a new execute result.
321    pub fn new(rows_affected: u64) -> Self {
322        Self {
323            rows_affected,
324            output_params: Vec::new(),
325        }
326    }
327
328    /// Create a result with output parameters.
329    pub fn with_outputs(rows_affected: u64, output_params: Vec<OutputParam>) -> Self {
330        Self {
331            rows_affected,
332            output_params,
333        }
334    }
335
336    /// Get an output parameter by name.
337    #[must_use]
338    pub fn get_output(&self, name: &str) -> Option<&OutputParam> {
339        self.output_params
340            .iter()
341            .find(|p| p.name.eq_ignore_ascii_case(name))
342    }
343}
344
345/// Result of a stored procedure execution.
346///
347/// Contains the return value, affected row count, output parameters,
348/// and any result sets produced by the procedure.
349///
350/// # Example
351///
352/// ```rust,no_run
353/// # async fn ex(client: &mut mssql_client::Client<mssql_client::Ready>) -> Result<(), mssql_client::Error> {
354/// let result = client.call_procedure("dbo.GetUser", &[&1i32]).await?;
355///
356/// // Check the return value (RETURN statement in the proc)
357/// assert_eq!(result.return_value, 0);
358///
359/// // Process result sets
360/// for mut rs in result.result_sets {
361///     while let Some(row) = rs.next_row() {
362///         let row = row?;
363///         println!("{:?}", row);
364///     }
365/// }
366/// # Ok(())
367/// # }
368/// ```
369#[derive(Debug, Clone)]
370#[non_exhaustive]
371#[must_use]
372pub struct ProcedureResult {
373    /// Return value from the stored procedure's RETURN statement.
374    ///
375    /// Defaults to 0 if the procedure does not explicitly return a value,
376    /// which matches SQL Server's default behavior.
377    pub return_value: i32,
378    /// Total number of rows affected by statements within the procedure.
379    pub rows_affected: u64,
380    /// Output parameters returned by the procedure.
381    pub output_params: Vec<OutputParam>,
382    /// Result sets produced by SELECT statements within the procedure.
383    pub result_sets: Vec<ResultSet>,
384}
385
386impl ProcedureResult {
387    /// Create a new empty procedure result.
388    pub(crate) fn new() -> Self {
389        Self {
390            return_value: 0,
391            rows_affected: 0,
392            output_params: Vec::new(),
393            result_sets: Vec::new(),
394        }
395    }
396
397    /// Get the return value from the stored procedure.
398    ///
399    /// This is the value from the procedure's `RETURN` statement.
400    /// Defaults to 0 if not explicitly set by the procedure.
401    #[must_use]
402    pub fn get_return_value(&self) -> i32 {
403        self.return_value
404    }
405
406    /// Get an output parameter by name (case-insensitive).
407    ///
408    /// Strips the `@` prefix from both the search name and stored names
409    /// before comparing, so `get_output("result")` and `get_output("@result")`
410    /// are equivalent.
411    ///
412    /// # Example
413    ///
414    /// ```rust,no_run
415    /// # use mssql_client::SqlValue;
416    /// # async fn ex(client: &mut mssql_client::Client<mssql_client::Ready>) -> Result<(), mssql_client::Error> {
417    /// let result = client.procedure("dbo.CalculateSum")?
418    ///     .input("@a", &10i32)
419    ///     .input("@b", &20i32)
420    ///     .output_int("@result")
421    ///     .execute().await?;
422    ///
423    /// let output = result.get_output("@result").expect("output param exists");
424    /// assert_eq!(output.value, SqlValue::Int(30));
425    /// # Ok(())
426    /// # }
427    /// ```
428    #[must_use]
429    pub fn get_output(&self, name: &str) -> Option<&OutputParam> {
430        let search = name.strip_prefix('@').unwrap_or(name);
431        self.output_params.iter().find(|p| {
432            let stored = p.name.strip_prefix('@').unwrap_or(&p.name);
433            stored.eq_ignore_ascii_case(search)
434        })
435    }
436
437    /// Get the first result set, if any.
438    ///
439    /// Convenience method for procedures that return a single result set.
440    #[must_use]
441    pub fn first_result_set(&self) -> Option<&ResultSet> {
442        self.result_sets.first()
443    }
444
445    /// Check if the procedure produced any result sets.
446    #[must_use]
447    pub fn has_result_sets(&self) -> bool {
448        !self.result_sets.is_empty()
449    }
450}
451
452/// A single result set within a multi-result batch.
453///
454/// Rows are stored as `PendingRow` values that may be either already-decoded
455/// [`Row`]s (eager path, used by tests and direct construction) or raw TDS
456/// bytes (lazy path, used by [`crate::Client::call_procedure`] and
457/// [`crate::Client::query_multiple`]). Decoding happens on pull, so per-row
458/// decode errors surface through [`ResultSet::next_row`] and
459/// [`ResultSet::collect_all`].
460#[derive(Debug, Clone)]
461#[must_use]
462pub struct ResultSet {
463    /// Column metadata for this result set.
464    row_meta: Arc<crate::row::ColMetaData>,
465    /// Pending rows — either pre-parsed or raw TDS bytes awaiting decode.
466    pending_rows: VecDeque<PendingRow>,
467    /// Protocol metadata required to decode raw rows. `None` when every
468    /// pending row is already [`PendingRow::Parsed`] (eager path).
469    meta: Option<ColMetaData>,
470    /// Pre-resolved column decryptor for Always Encrypted result sets.
471    ///
472    /// Wrapped in `Arc` so cloning a [`ResultSet`] stays cheap (clones share
473    /// the underlying decryptor state instead of duplicating derived keys).
474    #[cfg(feature = "always-encrypted")]
475    decryptor: Option<std::sync::Arc<crate::column_decryptor::ColumnDecryptor>>,
476}
477
478impl ResultSet {
479    /// Create a new result set from already-decoded rows.
480    ///
481    /// This is the eager constructor used by tests and callers that already
482    /// hold typed [`Row`]s. Production query paths use `ResultSet::from_raw`
483    /// (private) to defer row decoding.
484    pub fn new(columns: Vec<Column>, rows: Vec<Row>) -> Self {
485        Self {
486            row_meta: Arc::new(crate::row::ColMetaData::new(columns)),
487            pending_rows: rows.into_iter().map(PendingRow::Parsed).collect(),
488            meta: None,
489            #[cfg(feature = "always-encrypted")]
490            decryptor: None,
491        }
492    }
493
494    /// Create a result set from raw row bytes and protocol metadata.
495    ///
496    /// Rows are decoded on demand as the caller pulls them via
497    /// [`ResultSet::next_row`] or [`ResultSet::collect_all`]. The `meta` must
498    /// describe every row in `pending`. If decryption is configured,
499    /// `decryptor` must cover the same column set.
500    pub(crate) fn from_raw(
501        columns: Vec<Column>,
502        pending: Vec<PendingRow>,
503        meta: ColMetaData,
504        #[cfg(feature = "always-encrypted")] decryptor: Option<
505            std::sync::Arc<crate::column_decryptor::ColumnDecryptor>,
506        >,
507    ) -> Self {
508        Self {
509            row_meta: Arc::new(crate::row::ColMetaData::new(columns)),
510            pending_rows: pending.into(),
511            meta: Some(meta),
512            #[cfg(feature = "always-encrypted")]
513            decryptor,
514        }
515    }
516
517    /// Get the column metadata.
518    #[must_use]
519    pub fn columns(&self) -> &[Column] {
520        &self.row_meta.columns
521    }
522
523    /// Get the number of rows remaining.
524    #[must_use]
525    pub fn rows_remaining(&self) -> usize {
526        self.pending_rows.len()
527    }
528
529    /// Get the next row from this result set.
530    ///
531    /// Returns `None` when no more rows remain, or `Some(Err(_))` when the
532    /// next pending row fails to decode. The stream is not short-circuited
533    /// on decode error — the caller may continue to pull subsequent rows.
534    pub fn next_row(&mut self) -> Option<Result<Row, Error>> {
535        self.pending_rows.pop_front().map(|p| self.decode(p))
536    }
537
538    /// Check if this result set is empty.
539    #[must_use]
540    pub fn is_empty(&self) -> bool {
541        self.pending_rows.is_empty()
542    }
543
544    /// Collect all remaining rows into a vector.
545    ///
546    /// Stops at the first decode error and returns it.
547    pub fn collect_all(&mut self) -> Result<Vec<Row>, Error> {
548        let mut out = Vec::with_capacity(self.pending_rows.len());
549        while let Some(pending) = self.pending_rows.pop_front() {
550            out.push(self.decode(pending)?);
551        }
552        Ok(out)
553    }
554
555    /// Decode a pending row into a typed [`Row`].
556    fn decode(&self, pending: PendingRow) -> Result<Row, Error> {
557        match pending {
558            PendingRow::Parsed(row) => Ok(row),
559            PendingRow::Raw(raw) => {
560                let meta = self
561                    .meta
562                    .as_ref()
563                    .ok_or_else(|| Error::Protocol("row metadata missing for raw row".into()))?;
564                #[cfg(feature = "always-encrypted")]
565                if let Some(ref dec) = self.decryptor {
566                    return crate::column_parser::convert_raw_row_decrypted(
567                        &raw,
568                        meta,
569                        &self.row_meta,
570                        dec,
571                    );
572                }
573                crate::column_parser::convert_raw_row(&raw, meta, &self.row_meta)
574            }
575            PendingRow::Nbc(nbc) => {
576                let meta = self
577                    .meta
578                    .as_ref()
579                    .ok_or_else(|| Error::Protocol("row metadata missing for NBC row".into()))?;
580                #[cfg(feature = "always-encrypted")]
581                if let Some(ref dec) = self.decryptor {
582                    return crate::column_parser::convert_nbc_row_decrypted(
583                        &nbc,
584                        meta,
585                        &self.row_meta,
586                        dec,
587                    );
588                }
589                crate::column_parser::convert_nbc_row(&nbc, meta, &self.row_meta)
590            }
591        }
592    }
593
594    /// Consume this result set and produce a [`QueryStream`] that carries the
595    /// same pending rows and decode state.
596    ///
597    /// Used by [`MultiResultStream::into_query_streams`] — avoids eagerly
598    /// materializing rows when the caller wants stream-level ergonomics.
599    fn into_query_stream<'a>(self) -> QueryStream<'a> {
600        QueryStream {
601            row_meta: self.row_meta,
602            rows: self.pending_rows,
603            meta: self.meta,
604            #[cfg(feature = "always-encrypted")]
605            decryptor: self.decryptor,
606            finished: false,
607            _marker: std::marker::PhantomData,
608        }
609    }
610}
611
612/// Multiple result sets from a batch or stored procedure.
613///
614/// Some queries return multiple result sets (e.g., stored procedures
615/// with multiple SELECT statements, or batches with multiple queries).
616///
617/// # Example
618///
619/// ```rust,no_run
620/// # async fn ex(client: &mut mssql_client::Client<mssql_client::Ready>) -> Result<(), mssql_client::Error> {
621/// // Execute a batch with multiple SELECT statements
622/// let mut results = client.query_multiple("SELECT 1 AS a; SELECT 2 AS b, 3 AS c;", &[]).await?;
623///
624/// // Process first result set
625/// while let Some(row) = results.next_row().await? {
626///     println!("Result 1: {:?}", row);
627/// }
628///
629/// // Move to second result set
630/// if results.next_result().await? {
631///     while let Some(row) = results.next_row().await? {
632///         println!("Result 2: {:?}", row);
633///     }
634/// }
635/// # Ok(())
636/// # }
637/// ```
638#[must_use = "streams must be consumed; dropping a stream discards remaining results"]
639pub struct MultiResultStream<'a> {
640    /// All result sets from the batch.
641    result_sets: Vec<ResultSet>,
642    /// Current result set index (0-based).
643    current_result: usize,
644    /// Lifetime tied to the connection.
645    _marker: std::marker::PhantomData<&'a ()>,
646}
647
648impl<'a> MultiResultStream<'a> {
649    /// Create a new multi-result stream from parsed result sets.
650    pub(crate) fn new(result_sets: Vec<ResultSet>) -> Self {
651        Self {
652            result_sets,
653            current_result: 0,
654            _marker: std::marker::PhantomData,
655        }
656    }
657
658    /// Get the current result set index (0-based).
659    #[must_use]
660    pub fn current_result_index(&self) -> usize {
661        self.current_result
662    }
663
664    /// Get the total number of result sets.
665    #[must_use]
666    pub fn result_count(&self) -> usize {
667        self.result_sets.len()
668    }
669
670    /// Check if there are more result sets after the current one.
671    #[must_use]
672    pub fn has_more_results(&self) -> bool {
673        self.current_result + 1 < self.result_sets.len()
674    }
675
676    /// Get the column metadata for the current result set.
677    ///
678    /// Returns `None` if there are no result sets or we've moved past all of them.
679    #[must_use]
680    pub fn columns(&self) -> Option<&[Column]> {
681        self.result_sets
682            .get(self.current_result)
683            .map(|rs| rs.columns())
684    }
685
686    /// Move to the next result set.
687    ///
688    /// Returns `true` if there is another result set, `false` if no more.
689    pub async fn next_result(&mut self) -> Result<bool, Error> {
690        if self.current_result + 1 < self.result_sets.len() {
691            self.current_result += 1;
692            Ok(true)
693        } else {
694            Ok(false)
695        }
696    }
697
698    /// Get the next row from the current result set.
699    ///
700    /// Returns `None` when no more rows in the current result set.
701    /// Call `next_result()` to move to the next result set.
702    ///
703    /// Per-row decode errors (from lazy row decoding) surface here as
704    /// `Err(_)`. Pre-2.9 this reader decoded rows eagerly and decode errors
705    /// surfaced at `query_multiple().await?` instead.
706    pub async fn next_row(&mut self) -> Result<Option<Row>, Error> {
707        if let Some(result_set) = self.result_sets.get_mut(self.current_result) {
708            result_set.next_row().transpose()
709        } else {
710            Ok(None)
711        }
712    }
713
714    /// Get a mutable reference to the current result set.
715    #[must_use]
716    pub fn current_result_set(&mut self) -> Option<&mut ResultSet> {
717        self.result_sets.get_mut(self.current_result)
718    }
719
720    /// Collect all rows from the current result set.
721    ///
722    /// Returns `Ok(vec![])` if the current result index is out of range
723    /// (e.g., all result sets have been consumed). Propagates decode errors
724    /// from the underlying lazy row parser.
725    pub fn collect_current(&mut self) -> Result<Vec<Row>, Error> {
726        match self.result_sets.get_mut(self.current_result) {
727            Some(rs) => rs.collect_all(),
728            None => Ok(Vec::new()),
729        }
730    }
731
732    /// Consume the stream and return all result sets as `QueryStream`s.
733    pub fn into_query_streams(self) -> Vec<QueryStream<'a>> {
734        self.result_sets
735            .into_iter()
736            .map(ResultSet::into_query_stream)
737            .collect()
738    }
739}
740
741#[cfg(test)]
742#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
743mod tests {
744    use super::*;
745
746    #[test]
747    fn test_execute_result() {
748        let result = ExecuteResult::new(42);
749        assert_eq!(result.rows_affected, 42);
750        assert!(result.output_params.is_empty());
751    }
752
753    #[test]
754    fn test_procedure_result_defaults() {
755        let result = ProcedureResult::new();
756        assert_eq!(result.return_value, 0);
757        assert_eq!(result.rows_affected, 0);
758        assert!(result.output_params.is_empty());
759        assert!(result.result_sets.is_empty());
760        assert!(!result.has_result_sets());
761        assert!(result.first_result_set().is_none());
762    }
763
764    #[test]
765    fn test_procedure_result_get_output() {
766        let mut result = ProcedureResult::new();
767        result.output_params.push(OutputParam {
768            name: "@Total".to_string(),
769            value: mssql_types::SqlValue::Int(42),
770        });
771        result.output_params.push(OutputParam {
772            name: "@Message".to_string(),
773            value: mssql_types::SqlValue::String("ok".to_string()),
774        });
775
776        // Exact match (case-insensitive)
777        assert!(result.get_output("@Total").is_some());
778        assert!(result.get_output("@total").is_some());
779        assert!(result.get_output("@TOTAL").is_some());
780
781        // @ prefix stripping
782        assert!(result.get_output("Total").is_some());
783        assert!(result.get_output("total").is_some());
784
785        // Non-existent
786        assert!(result.get_output("@NotHere").is_none());
787        assert!(result.get_output("NotHere").is_none());
788    }
789
790    #[test]
791    fn test_procedure_result_with_result_sets() {
792        use mssql_types::SqlValue;
793
794        let columns = vec![Column {
795            name: "id".to_string(),
796            index: 0,
797            type_name: "INT".to_string(),
798            nullable: false,
799            max_length: Some(4),
800            precision: None,
801            scale: None,
802            collation: None,
803        }];
804        let rows = vec![Row::from_values(columns.clone(), vec![SqlValue::Int(1)])];
805        let rs = ResultSet::new(columns, rows);
806
807        let mut result = ProcedureResult::new();
808        result.result_sets.push(rs);
809        result.return_value = 7;
810        result.rows_affected = 5;
811
812        assert!(result.has_result_sets());
813        assert_eq!(result.get_return_value(), 7);
814        assert_eq!(result.first_result_set().unwrap().columns().len(), 1);
815    }
816
817    #[test]
818    fn test_execute_result_with_outputs() {
819        let outputs = vec![OutputParam {
820            name: "ReturnValue".to_string(),
821            value: mssql_types::SqlValue::Int(100),
822        }];
823
824        let result = ExecuteResult::with_outputs(10, outputs);
825        assert_eq!(result.rows_affected, 10);
826        assert!(result.get_output("ReturnValue").is_some());
827        assert!(result.get_output("returnvalue").is_some()); // case-insensitive
828        assert!(result.get_output("NotFound").is_none());
829    }
830
831    #[test]
832    fn test_query_stream_columns() {
833        let columns = vec![Column {
834            name: "id".to_string(),
835            index: 0,
836            type_name: "INT".to_string(),
837            nullable: false,
838            max_length: Some(4),
839            precision: Some(0),
840            scale: Some(0),
841            collation: None,
842        }];
843
844        let stream = QueryStream::new(columns, Vec::new());
845        assert_eq!(stream.columns().len(), 1);
846        assert_eq!(stream.columns()[0].name, "id");
847        assert!(!stream.is_finished());
848    }
849
850    #[test]
851    fn test_query_stream_with_rows() {
852        use mssql_types::SqlValue;
853
854        let columns = vec![
855            Column {
856                name: "id".to_string(),
857                index: 0,
858                type_name: "INT".to_string(),
859                nullable: false,
860                max_length: Some(4),
861                precision: None,
862                scale: None,
863                collation: None,
864            },
865            Column {
866                name: "name".to_string(),
867                index: 1,
868                type_name: "NVARCHAR".to_string(),
869                nullable: true,
870                max_length: Some(100),
871                precision: None,
872                scale: None,
873                collation: None,
874            },
875        ];
876
877        let rows = vec![
878            Row::from_values(
879                columns.clone(),
880                vec![SqlValue::Int(1), SqlValue::String("Alice".to_string())],
881            ),
882            Row::from_values(
883                columns.clone(),
884                vec![SqlValue::Int(2), SqlValue::String("Bob".to_string())],
885            ),
886        ];
887
888        let mut stream = QueryStream::new(columns, rows);
889        assert_eq!(stream.columns().len(), 2);
890        assert_eq!(stream.rows_remaining(), 2);
891        assert!(!stream.is_finished());
892
893        // First row
894        let row1 = stream.try_next().unwrap();
895        assert_eq!(row1.get::<i32>(0).unwrap(), 1);
896        assert_eq!(row1.get_by_name::<String>("name").unwrap(), "Alice");
897
898        // Second row
899        let row2 = stream.try_next().unwrap();
900        assert_eq!(row2.get::<i32>(0).unwrap(), 2);
901        assert_eq!(row2.get_by_name::<String>("name").unwrap(), "Bob");
902
903        // No more rows
904        assert!(stream.try_next().is_none());
905        assert!(stream.is_finished());
906    }
907
908    #[test]
909    fn test_query_stream_iterator() {
910        use mssql_types::SqlValue;
911
912        let columns = vec![Column {
913            name: "val".to_string(),
914            index: 0,
915            type_name: "INT".to_string(),
916            nullable: false,
917            max_length: None,
918            precision: None,
919            scale: None,
920            collation: None,
921        }];
922
923        let rows = vec![
924            Row::from_values(columns.clone(), vec![SqlValue::Int(10)]),
925            Row::from_values(columns.clone(), vec![SqlValue::Int(20)]),
926            Row::from_values(columns.clone(), vec![SqlValue::Int(30)]),
927        ];
928
929        let mut stream = QueryStream::new(columns, rows);
930
931        // Use iterator — unwrap each Result so test failures are visible
932        // (QueryStream's Iterator impl always yields Ok, but we should
933        // not silently swallow errors if that ever changes)
934        let values: Vec<i32> = stream
935            .by_ref()
936            .map(|r| r.unwrap().get::<i32>(0).unwrap())
937            .collect();
938
939        assert_eq!(values, vec![10, 20, 30]);
940        assert!(stream.is_finished());
941    }
942
943    #[test]
944    fn test_query_stream_empty() {
945        let stream = QueryStream::empty();
946        assert!(stream.columns().is_empty());
947        assert_eq!(stream.rows_remaining(), 0);
948        assert!(stream.is_finished());
949    }
950
951    /// Exercises the lazy-decode path: rows are stored as raw TDS bytes and
952    /// decoded only when the caller pulls them. Mirrors what
953    /// `read_query_response` now produces and pins the contract between
954    /// `PendingRow::Raw` and the per-row decode in `poll_next`/`next`.
955    #[test]
956    fn test_query_stream_lazy_raw_row_decoding() {
957        use bytes::Bytes;
958        use tds_protocol::token::{ColMetaData, ColumnData, RawRow, TypeInfo};
959        use tds_protocol::types::TypeId;
960
961        // Build raw row bytes for two columns: IntN(42) + IntN(NULL).
962        let mut data = Vec::new();
963        data.push(4); // IntN length prefix — 4 bytes
964        data.extend_from_slice(&42i32.to_le_bytes());
965        data.push(0); // IntN NULL (zero-length)
966
967        let meta = ColMetaData {
968            columns: vec![
969                ColumnData {
970                    name: "a".to_string(),
971                    type_id: TypeId::IntN,
972                    col_type: 0x26,
973                    flags: 0x00,
974                    user_type: 0,
975                    type_info: TypeInfo {
976                        max_length: Some(4),
977                        precision: None,
978                        scale: None,
979                        collation: None,
980                    },
981                    crypto_metadata: None,
982                },
983                ColumnData {
984                    name: "b".to_string(),
985                    type_id: TypeId::IntN,
986                    col_type: 0x26,
987                    flags: 0x01,
988                    user_type: 0,
989                    type_info: TypeInfo {
990                        max_length: Some(4),
991                        precision: None,
992                        scale: None,
993                        collation: None,
994                    },
995                    crypto_metadata: None,
996                },
997            ],
998            cek_table: None,
999        };
1000
1001        let columns = vec![
1002            Column {
1003                name: "a".to_string(),
1004                index: 0,
1005                type_name: "INT".to_string(),
1006                nullable: false,
1007                max_length: Some(4),
1008                precision: None,
1009                scale: None,
1010                collation: None,
1011            },
1012            Column {
1013                name: "b".to_string(),
1014                index: 1,
1015                type_name: "INT".to_string(),
1016                nullable: true,
1017                max_length: Some(4),
1018                precision: None,
1019                scale: None,
1020                collation: None,
1021            },
1022        ];
1023
1024        let pending = vec![PendingRow::Raw(RawRow {
1025            data: Bytes::from(data),
1026        })];
1027
1028        #[cfg(feature = "always-encrypted")]
1029        let mut stream = QueryStream::from_raw(columns, pending, meta, None);
1030        #[cfg(not(feature = "always-encrypted"))]
1031        let mut stream = QueryStream::from_raw(columns, pending, meta);
1032
1033        assert_eq!(stream.rows_remaining(), 1);
1034        let row = stream
1035            .next()
1036            .expect("one row pending")
1037            .expect("row decoded successfully");
1038        assert_eq!(row.get::<i32>(0).unwrap(), 42);
1039        assert!(row.is_null(1));
1040        assert!(stream.next().is_none());
1041        assert!(stream.is_finished());
1042    }
1043
1044    /// Decoder errors must surface per-row via `Stream`/`Iterator` without
1045    /// derailing the stream state. Truncated raw bytes trigger a decode
1046    /// error that the caller observes as `Some(Err(_))`.
1047    #[test]
1048    fn test_query_stream_lazy_decode_error_propagates() {
1049        use bytes::Bytes;
1050        use tds_protocol::token::{ColMetaData, ColumnData, RawRow, TypeInfo};
1051        use tds_protocol::types::TypeId;
1052
1053        // Declare an Int4 column but provide only 2 bytes — decode must fail.
1054        let data = vec![0x01u8, 0x02];
1055
1056        let meta = ColMetaData {
1057            columns: vec![ColumnData {
1058                name: "a".to_string(),
1059                type_id: TypeId::Int4,
1060                col_type: 0x38,
1061                flags: 0x00,
1062                user_type: 0,
1063                type_info: TypeInfo {
1064                    max_length: Some(4),
1065                    precision: None,
1066                    scale: None,
1067                    collation: None,
1068                },
1069                crypto_metadata: None,
1070            }],
1071            cek_table: None,
1072        };
1073
1074        let columns = vec![Column {
1075            name: "a".to_string(),
1076            index: 0,
1077            type_name: "INT".to_string(),
1078            nullable: false,
1079            max_length: Some(4),
1080            precision: None,
1081            scale: None,
1082            collation: None,
1083        }];
1084
1085        let pending = vec![PendingRow::Raw(RawRow {
1086            data: Bytes::from(data),
1087        })];
1088
1089        #[cfg(feature = "always-encrypted")]
1090        let mut stream = QueryStream::from_raw(columns, pending, meta, None);
1091        #[cfg(not(feature = "always-encrypted"))]
1092        let mut stream = QueryStream::from_raw(columns, pending, meta);
1093
1094        let item = stream.next().expect("pending row present");
1095        assert!(item.is_err(), "truncated bytes must surface a decode error");
1096        assert!(stream.next().is_none());
1097    }
1098
1099    /// Helper to build a single-column IntN metadata block for the lazy
1100    /// `ResultSet` / `MultiResultStream` tests below.
1101    #[cfg(test)]
1102    fn intn_meta_and_columns(
1103        col_name: &str,
1104        nullable: bool,
1105    ) -> (tds_protocol::token::ColMetaData, Vec<Column>) {
1106        use tds_protocol::token::{ColMetaData, ColumnData, TypeInfo};
1107        use tds_protocol::types::TypeId;
1108        (
1109            ColMetaData {
1110                columns: vec![ColumnData {
1111                    name: col_name.to_string(),
1112                    type_id: TypeId::IntN,
1113                    col_type: 0x26,
1114                    flags: if nullable { 0x01 } else { 0x00 },
1115                    user_type: 0,
1116                    type_info: TypeInfo {
1117                        max_length: Some(4),
1118                        precision: None,
1119                        scale: None,
1120                        collation: None,
1121                    },
1122                    crypto_metadata: None,
1123                }],
1124                cek_table: None,
1125            },
1126            vec![Column {
1127                name: col_name.to_string(),
1128                index: 0,
1129                type_name: "INT".to_string(),
1130                nullable,
1131                max_length: Some(4),
1132                precision: None,
1133                scale: None,
1134                collation: None,
1135            }],
1136        )
1137    }
1138
1139    /// Exercises the `ResultSet` lazy-decode path introduced in 2.9.
1140    /// Mirrors `test_query_stream_lazy_raw_row_decoding` but via the
1141    /// result-set API that `call_procedure` / `query_multiple` expose.
1142    #[test]
1143    fn test_result_set_lazy_raw_row_decoding() {
1144        use bytes::Bytes;
1145        use tds_protocol::token::RawRow;
1146
1147        let (meta, columns) = intn_meta_and_columns("a", false);
1148
1149        // Two rows: 7 and 11 encoded as IntN(4).
1150        let pending = vec![
1151            PendingRow::Raw(RawRow {
1152                data: {
1153                    let mut b = Vec::with_capacity(5);
1154                    b.push(4);
1155                    b.extend_from_slice(&7i32.to_le_bytes());
1156                    Bytes::from(b)
1157                },
1158            }),
1159            PendingRow::Raw(RawRow {
1160                data: {
1161                    let mut b = Vec::with_capacity(5);
1162                    b.push(4);
1163                    b.extend_from_slice(&11i32.to_le_bytes());
1164                    Bytes::from(b)
1165                },
1166            }),
1167        ];
1168
1169        #[cfg(feature = "always-encrypted")]
1170        let mut rs = ResultSet::from_raw(columns, pending, meta, None);
1171        #[cfg(not(feature = "always-encrypted"))]
1172        let mut rs = ResultSet::from_raw(columns, pending, meta);
1173
1174        assert_eq!(rs.rows_remaining(), 2);
1175        assert!(!rs.is_empty());
1176
1177        let row1 = rs.next_row().expect("row present").expect("decodes");
1178        assert_eq!(row1.get::<i32>(0).unwrap(), 7);
1179
1180        let row2 = rs.next_row().expect("row present").expect("decodes");
1181        assert_eq!(row2.get::<i32>(0).unwrap(), 11);
1182
1183        assert!(rs.next_row().is_none());
1184        assert!(rs.is_empty());
1185    }
1186
1187    /// Decoder errors in `ResultSet::next_row` must surface per-row without
1188    /// derailing further calls. Same contract as
1189    /// `test_query_stream_lazy_decode_error_propagates`.
1190    #[test]
1191    fn test_result_set_lazy_decode_error_propagates() {
1192        use bytes::Bytes;
1193        use tds_protocol::token::{ColMetaData, ColumnData, RawRow, TypeInfo};
1194        use tds_protocol::types::TypeId;
1195
1196        // Int4 (not IntN) with only 2 bytes → decode must fail.
1197        let meta = ColMetaData {
1198            columns: vec![ColumnData {
1199                name: "a".to_string(),
1200                type_id: TypeId::Int4,
1201                col_type: 0x38,
1202                flags: 0x00,
1203                user_type: 0,
1204                type_info: TypeInfo {
1205                    max_length: Some(4),
1206                    precision: None,
1207                    scale: None,
1208                    collation: None,
1209                },
1210                crypto_metadata: None,
1211            }],
1212            cek_table: None,
1213        };
1214        let columns = vec![Column {
1215            name: "a".to_string(),
1216            index: 0,
1217            type_name: "INT".to_string(),
1218            nullable: false,
1219            max_length: Some(4),
1220            precision: None,
1221            scale: None,
1222            collation: None,
1223        }];
1224
1225        let pending = vec![PendingRow::Raw(RawRow {
1226            data: Bytes::from(vec![0x01u8, 0x02]),
1227        })];
1228
1229        #[cfg(feature = "always-encrypted")]
1230        let mut rs = ResultSet::from_raw(columns, pending, meta, None);
1231        #[cfg(not(feature = "always-encrypted"))]
1232        let mut rs = ResultSet::from_raw(columns, pending, meta);
1233
1234        let first = rs.next_row().expect("pending row present");
1235        assert!(
1236            first.is_err(),
1237            "truncated bytes must surface a decode error"
1238        );
1239        assert!(rs.next_row().is_none());
1240    }
1241
1242    /// `collect_all` on a lazy `ResultSet` decodes every pending row and
1243    /// propagates the first decode error. Ensures 2.9's signature change is
1244    /// exercised end-to-end.
1245    #[test]
1246    fn test_result_set_lazy_collect_all_success_and_error() {
1247        use bytes::Bytes;
1248        use tds_protocol::token::RawRow;
1249
1250        // Success: two rows decode cleanly.
1251        let (meta_ok, cols_ok) = intn_meta_and_columns("a", false);
1252        let pending_ok = vec![
1253            PendingRow::Raw(RawRow {
1254                data: {
1255                    let mut b = Vec::with_capacity(5);
1256                    b.push(4);
1257                    b.extend_from_slice(&10i32.to_le_bytes());
1258                    Bytes::from(b)
1259                },
1260            }),
1261            PendingRow::Raw(RawRow {
1262                data: {
1263                    let mut b = Vec::with_capacity(5);
1264                    b.push(4);
1265                    b.extend_from_slice(&20i32.to_le_bytes());
1266                    Bytes::from(b)
1267                },
1268            }),
1269        ];
1270
1271        #[cfg(feature = "always-encrypted")]
1272        let mut rs_ok = ResultSet::from_raw(cols_ok, pending_ok, meta_ok, None);
1273        #[cfg(not(feature = "always-encrypted"))]
1274        let mut rs_ok = ResultSet::from_raw(cols_ok, pending_ok, meta_ok);
1275        let rows = rs_ok.collect_all().expect("all rows decode");
1276        assert_eq!(rows.len(), 2);
1277        assert_eq!(rows[0].get::<i32>(0).unwrap(), 10);
1278        assert_eq!(rows[1].get::<i32>(0).unwrap(), 20);
1279        assert!(rs_ok.is_empty());
1280
1281        // Error: a truncated row (declared Int4 with only 2 bytes) makes
1282        // collect_all fail. collect_all short-circuits on the first Err.
1283        use tds_protocol::token::{ColMetaData, ColumnData, TypeInfo};
1284        use tds_protocol::types::TypeId;
1285        let meta_err = ColMetaData {
1286            columns: vec![ColumnData {
1287                name: "a".to_string(),
1288                type_id: TypeId::Int4,
1289                col_type: 0x38,
1290                flags: 0x00,
1291                user_type: 0,
1292                type_info: TypeInfo {
1293                    max_length: Some(4),
1294                    precision: None,
1295                    scale: None,
1296                    collation: None,
1297                },
1298                crypto_metadata: None,
1299            }],
1300            cek_table: None,
1301        };
1302        let cols_err = vec![Column {
1303            name: "a".to_string(),
1304            index: 0,
1305            type_name: "INT".to_string(),
1306            nullable: false,
1307            max_length: Some(4),
1308            precision: None,
1309            scale: None,
1310            collation: None,
1311        }];
1312        let pending_err = vec![PendingRow::Raw(RawRow {
1313            data: Bytes::from(vec![0x01u8, 0x02]),
1314        })];
1315
1316        #[cfg(feature = "always-encrypted")]
1317        let mut rs_err = ResultSet::from_raw(cols_err, pending_err, meta_err, None);
1318        #[cfg(not(feature = "always-encrypted"))]
1319        let mut rs_err = ResultSet::from_raw(cols_err, pending_err, meta_err);
1320        let err = rs_err.collect_all();
1321        assert!(err.is_err(), "collect_all must propagate decode error");
1322    }
1323
1324    /// `MultiResultStream` end-to-end lazy-decode path: two lazy `ResultSet`s
1325    /// decoded on demand as the caller walks through via `next_row` /
1326    /// `next_result`. Pins the 2.9 refactor of `read_multi_result_response`.
1327    #[tokio::test]
1328    async fn test_multi_result_stream_lazy_decode_across_result_sets() {
1329        use bytes::Bytes;
1330        use tds_protocol::token::RawRow;
1331
1332        let (meta1, cols1) = intn_meta_and_columns("a", false);
1333        let pending1 = vec![PendingRow::Raw(RawRow {
1334            data: {
1335                let mut b = Vec::with_capacity(5);
1336                b.push(4);
1337                b.extend_from_slice(&101i32.to_le_bytes());
1338                Bytes::from(b)
1339            },
1340        })];
1341        #[cfg(feature = "always-encrypted")]
1342        let rs1 = ResultSet::from_raw(cols1, pending1, meta1, None);
1343        #[cfg(not(feature = "always-encrypted"))]
1344        let rs1 = ResultSet::from_raw(cols1, pending1, meta1);
1345
1346        let (meta2, cols2) = intn_meta_and_columns("b", false);
1347        let pending2 = vec![PendingRow::Raw(RawRow {
1348            data: {
1349                let mut b = Vec::with_capacity(5);
1350                b.push(4);
1351                b.extend_from_slice(&202i32.to_le_bytes());
1352                Bytes::from(b)
1353            },
1354        })];
1355        #[cfg(feature = "always-encrypted")]
1356        let rs2 = ResultSet::from_raw(cols2, pending2, meta2, None);
1357        #[cfg(not(feature = "always-encrypted"))]
1358        let rs2 = ResultSet::from_raw(cols2, pending2, meta2);
1359
1360        let mut stream = MultiResultStream::new(vec![rs1, rs2]);
1361        assert_eq!(stream.result_count(), 2);
1362        assert_eq!(stream.current_result_index(), 0);
1363
1364        let row = stream
1365            .next_row()
1366            .await
1367            .expect("first row success")
1368            .expect("row present");
1369        assert_eq!(row.get::<i32>(0).unwrap(), 101);
1370        assert!(stream.next_row().await.expect("no more rows").is_none());
1371
1372        assert!(stream.has_more_results());
1373        assert!(stream.next_result().await.expect("advance ok"));
1374        assert_eq!(stream.current_result_index(), 1);
1375
1376        let row = stream
1377            .next_row()
1378            .await
1379            .expect("second row success")
1380            .expect("row present");
1381        assert_eq!(row.get::<i32>(0).unwrap(), 202);
1382    }
1383}