Skip to main content

tds_driver/
result.rs

1pub use crate::tds::stream::{CommandItem, QueryItem, ResultMetadata};
2use crate::{
3    client::Connection,
4    error::Error,
5    tds::stream::{CommandReturnValue, TokenStream},
6    FromSql, Row,
7};
8use futures_util::io::{AsyncRead, AsyncWrite};
9use futures_util::stream::TryStreamExt;
10use std::fmt::Debug;
11
12/// A result from a query execution, listing the number of affected rows.
13///
14/// If executing multiple queries, the resulting counts will be come separately,
15/// marking the rows affected for each query.
16///
17/// # Example
18///
19/// ```no_run
20/// # use tds_driver::Config;
21/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
22/// # use std::env;
23/// # #[tokio::main]
24/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
25/// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
26/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
27/// # );
28/// # let config = Config::from_ado_string(&c_str)?;
29/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
30/// # tcp.set_nodelay(true)?;
31/// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
32/// let result = client
33///     .execute(
34///         "INSERT INTO #Test (id) VALUES (@P1); INSERT INTO #Test (id) VALUES (@P2, @P3)",
35///         &[&1i32, &2i32, &3i32],
36///     )
37///     .await?;
38///
39/// assert_eq!(&[1, 2], result.rows_affected());
40/// # Ok(())
41/// # }
42/// ```
43///
44/// [`Client`]: struct.Client.html
45/// [`Rows`]: struct.Row.html
46/// [`next_resultset`]: #method.next_resultset
47#[derive(Debug)]
48pub struct ExecuteResult {
49    rows_affected: Vec<u64>,
50}
51
52impl<'a> ExecuteResult {
53    pub(crate) async fn new<S: AsyncRead + AsyncWrite + Unpin + Send>(
54        connection: &'a mut Connection<S>,
55    ) -> crate::Result<Self> {
56        let mut token_stream = TokenStream::new(connection).try_unfold();
57        let mut rows_affected = Vec::new();
58
59        while let Some(token) = token_stream.try_next().await? {
60            rows_affected.extend(token.records_affected());
61        }
62
63        Ok(Self { rows_affected })
64    }
65
66    /// The rows affected by each INSERT, UPDATE, DELETE, MERGE or other
67    /// non-SELECT statement, in execution order, including those run by
68    /// procedures and triggers. SELECTs and variable assignments
69    /// (`SET @v = ..`, `SELECT @v = ..`) are left out, and statements under
70    /// `SET NOCOUNT ON` report nothing. This is what SqlClient's
71    /// `ExecuteNonQuery` counts.
72    pub fn rows_affected(&self) -> &[u64] {
73        self.rows_affected.as_slice()
74    }
75
76    /// The sum of [`rows_affected`](Self::rows_affected): SqlClient's
77    /// `ExecuteNonQuery` result, except that it is 0 rather than -1 when no
78    /// statement reported a count.
79    ///
80    /// # Example
81    ///
82    /// ```no_run
83    /// # use tds_driver::Config;
84    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
85    /// # use std::env;
86    /// # #[tokio::main]
87    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
88    /// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
89    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
90    /// # );
91    /// # let config = Config::from_ado_string(&c_str)?;
92    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
93    /// # tcp.set_nodelay(true)?;
94    /// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
95    /// let rows_affected = client
96    ///     .execute(
97    ///         "INSERT INTO #Test (id) VALUES (@P1); INSERT INTO #Test (id) VALUES (@P2, @P3)",
98    ///         &[&1i32, &2i32, &3i32],
99    ///     )
100    ///     .await?;
101    ///
102    /// assert_eq!(3, rows_affected.total());
103    /// # Ok(())
104    /// # }
105    pub fn total(self) -> u64 {
106        self.rows_affected.into_iter().sum()
107    }
108}
109
110impl IntoIterator for ExecuteResult {
111    type Item = u64;
112    type IntoIter = std::vec::IntoIter<Self::Item>;
113
114    fn into_iter(self) -> Self::IntoIter {
115        self.rows_affected.into_iter()
116    }
117}
118
119/// A materialized result from executing a [`Command`], carrying the number of
120/// affected rows, the return code, the values of any OUT parameters and any
121/// record sets returned by the command.
122///
123/// [`Command`]: crate::Command
124///
125/// # Example
126///
127/// ```no_run
128/// # use tds_driver::{Config, Command};
129/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
130/// # use std::env;
131/// # #[tokio::main]
132/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
133/// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
134/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
135/// # );
136/// # let config = Config::from_ado_string(&c_str)?;
137/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
138/// # tcp.set_nodelay(true)?;
139/// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
140/// let mut cmd = Command::new("dbo.usp_SomeStoredProc");
141///
142/// cmd.bind_param("@foo", 34i32);
143/// cmd.bind_out_param("@bar", "bar");
144/// let res = cmd.exec(&mut client).await?.into_command_result().await?;
145///
146/// let rv: Option<&str> = res.try_return_value("@bar")?;
147/// let rc = res.return_code();
148/// let ra = res.rows_affected();
149///
150/// let rs0 = res.to_query_result(0);
151/// # Ok(())
152/// # }
153/// ```
154///
155#[derive(Debug)]
156pub struct CommandResult {
157    pub(crate) rows_affected: Vec<u64>,
158    pub(crate) return_code: i32,
159    pub(crate) return_values: Vec<CommandReturnValue>,
160    pub(crate) query_results: Vec<Vec<Row>>,
161}
162
163impl<'a> CommandResult {
164    /// The rows affected by each non-SELECT statement the command ran, in
165    /// order; counted as [`ExecuteResult::rows_affected`] counts them.
166    pub fn rows_affected(&self) -> &[u64] {
167        self.rows_affected.as_slice()
168    }
169
170    /// The procedure's `RETURN` value; 0 if it returned none. Signed:
171    /// `RETURN -1` gives `-1`.
172    pub fn return_code(&self) -> i32 {
173        self.return_code
174    }
175
176    /// The number of returned values (OUT parameters) available.
177    pub fn return_values_len(&self) -> usize {
178        self.return_values.len()
179    }
180
181    /// Gets a returned value by its OUT parameter name, converting it to `T`.
182    /// Returns `None` if the value is `NULL`, and an error if no OUT parameter
183    /// with the given name was returned.
184    pub fn try_return_value<T>(&'a self, name: &str) -> crate::Result<Option<T>>
185    where
186        T: FromSql<'a>,
187    {
188        let col_data = self
189            .return_values
190            .iter()
191            .find(|p| p.name.eq(name))
192            .ok_or_else(|| {
193                Error::Conversion(format!("Could not find return value {}", name).into())
194            })?;
195
196        T::from_sql(&col_data.data)
197    }
198
199    /// Gets a returned record set by its zero-based index. Returns `None` if the
200    /// index is out of range.
201    pub fn to_query_result(&self, idx: usize) -> Option<&Vec<Row>> {
202        self.query_results.get(idx)
203    }
204}
205
206impl IntoIterator for CommandResult {
207    type Item = Vec<Row>;
208    type IntoIter = std::vec::IntoIter<Self::Item>;
209
210    fn into_iter(self) -> Self::IntoIter {
211        self.query_results.into_iter()
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::tds::codec::ColumnData;
219
220    impl ExecuteResult {
221        fn from_counts(counts: Vec<u64>) -> Self {
222            Self {
223                rows_affected: counts,
224            }
225        }
226    }
227
228    #[test]
229    fn execute_result_rows_affected_preserves_order_and_values() {
230        let res = ExecuteResult::from_counts(vec![3, 0, 7]);
231        assert_eq!(res.rows_affected(), &[3, 0, 7]);
232    }
233
234    #[test]
235    fn execute_result_total_sums_every_count() {
236        assert_eq!(ExecuteResult::from_counts(vec![3, 0, 7]).total(), 10);
237    }
238
239    #[test]
240    fn execute_result_into_iter_yields_each_count() {
241        let counts: Vec<u64> = ExecuteResult::from_counts(vec![5, 9]).into_iter().collect();
242        assert_eq!(counts, vec![5, 9]);
243    }
244
245    fn return_value(name: &str, value: i32) -> CommandReturnValue {
246        CommandReturnValue {
247            name: name.to_string(),
248            ord: 0,
249            data: ColumnData::I32(Some(value)),
250        }
251    }
252
253    fn command_result() -> CommandResult {
254        CommandResult {
255            rows_affected: vec![2, 4],
256            return_code: 7,
257            return_values: vec![return_value("@a", 1), return_value("@b", 42)],
258            // Two (empty) record sets so `to_query_result` has Some values to return.
259            query_results: vec![Vec::new(), Vec::new()],
260        }
261    }
262
263    #[test]
264    fn command_result_scalar_accessors() {
265        let res = command_result();
266        assert_eq!(res.rows_affected(), &[2, 4]);
267        assert_eq!(res.return_code(), 7);
268        assert_eq!(res.return_values_len(), 2);
269    }
270
271    #[test]
272    fn command_result_to_query_result_indexes_record_sets() {
273        let res = command_result();
274        assert!(res.to_query_result(0).is_some());
275        assert!(res.to_query_result(1).is_some());
276        assert!(res.to_query_result(2).is_none());
277    }
278
279    #[test]
280    fn command_result_try_return_value_reads_named_out_param() {
281        let res = command_result();
282        let got: Option<i32> = res.try_return_value("@b").unwrap();
283        assert_eq!(got, Some(42));
284        assert!(res.try_return_value::<i32>("@missing").is_err());
285    }
286
287    #[test]
288    fn command_result_into_iter_yields_each_record_set() {
289        assert_eq!(command_result().into_iter().count(), 2);
290    }
291}