Skip to main content

tiberius/
result.rs

1pub use crate::tds::stream::{CommandItem, QueryItem, ResultMetadata};
2use crate::{
3    client::Connection,
4    error::Error,
5    tds::stream::{CommandReturnValue, ReceivedToken, 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 tiberius::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("TIBERIUS_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 = tiberius::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            match token {
61                ReceivedToken::DoneProc(done) if done.is_final() => (),
62                ReceivedToken::DoneProc(done) => rows_affected.push(done.rows()),
63                ReceivedToken::DoneInProc(done) => rows_affected.push(done.rows()),
64                ReceivedToken::Done(done) => rows_affected.push(done.rows()),
65                _ => (),
66            }
67        }
68
69        Ok(Self { rows_affected })
70    }
71
72    /// A slice of numbers of rows affected in the same order as the given
73    /// queries.
74    pub fn rows_affected(&self) -> &[u64] {
75        self.rows_affected.as_slice()
76    }
77
78    /// Aggregates all resulting row counts into a sum.
79    ///
80    /// # Example
81    ///
82    /// ```no_run
83    /// # use tiberius::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("TIBERIUS_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 = tiberius::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 tiberius::{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("TIBERIUS_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 = tiberius::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: u32,
159    pub(crate) return_values: Vec<CommandReturnValue>,
160    pub(crate) query_results: Vec<Vec<Row>>,
161}
162
163impl<'a> CommandResult {
164    /// A slice of the numbers of rows affected, in the same order as the
165    /// statements ran by the command.
166    pub fn rows_affected(&self) -> &[u64] {
167        self.rows_affected.as_slice()
168    }
169
170    /// The return code of the command, as returned by the server.
171    pub fn return_code(&self) -> u32 {
172        self.return_code
173    }
174
175    /// The number of returned values (OUT parameters) available.
176    pub fn return_values_len(&self) -> usize {
177        self.return_values.len()
178    }
179
180    /// Gets a returned value by its OUT parameter name, converting it to `T`.
181    /// Returns `None` if the value is `NULL`, and an error if no OUT parameter
182    /// with the given name was returned.
183    pub fn try_return_value<T>(&'a self, name: &str) -> crate::Result<Option<T>>
184    where
185        T: FromSql<'a>,
186    {
187        let col_data = self
188            .return_values
189            .iter()
190            .find(|p| p.name.eq(name))
191            .ok_or_else(|| {
192                Error::Conversion(format!("Could not find return value {}", name).into())
193            })?;
194
195        T::from_sql(&col_data.data)
196    }
197
198    /// Gets a returned record set by its zero-based index. Returns `None` if the
199    /// index is out of range.
200    pub fn to_query_result(&self, idx: usize) -> Option<&Vec<Row>> {
201        self.query_results.get(idx)
202    }
203}
204
205impl IntoIterator for CommandResult {
206    type Item = Vec<Row>;
207    type IntoIter = std::vec::IntoIter<Self::Item>;
208
209    fn into_iter(self) -> Self::IntoIter {
210        self.query_results.into_iter()
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::tds::codec::ColumnData;
218
219    impl ExecuteResult {
220        fn from_counts(counts: Vec<u64>) -> Self {
221            Self {
222                rows_affected: counts,
223            }
224        }
225    }
226
227    #[test]
228    fn execute_result_rows_affected_preserves_order_and_values() {
229        let res = ExecuteResult::from_counts(vec![3, 0, 7]);
230        assert_eq!(res.rows_affected(), &[3, 0, 7]);
231    }
232
233    #[test]
234    fn execute_result_total_sums_every_count() {
235        assert_eq!(ExecuteResult::from_counts(vec![3, 0, 7]).total(), 10);
236    }
237
238    #[test]
239    fn execute_result_into_iter_yields_each_count() {
240        let counts: Vec<u64> = ExecuteResult::from_counts(vec![5, 9]).into_iter().collect();
241        assert_eq!(counts, vec![5, 9]);
242    }
243
244    fn return_value(name: &str, value: i32) -> CommandReturnValue {
245        CommandReturnValue {
246            name: name.to_string(),
247            ord: 0,
248            data: ColumnData::I32(Some(value)),
249        }
250    }
251
252    fn command_result() -> CommandResult {
253        CommandResult {
254            rows_affected: vec![2, 4],
255            return_code: 7,
256            return_values: vec![return_value("@a", 1), return_value("@b", 42)],
257            // Two (empty) record sets so `to_query_result` has Some values to return.
258            query_results: vec![Vec::new(), Vec::new()],
259        }
260    }
261
262    #[test]
263    fn command_result_scalar_accessors() {
264        let res = command_result();
265        assert_eq!(res.rows_affected(), &[2, 4]);
266        assert_eq!(res.return_code(), 7);
267        assert_eq!(res.return_values_len(), 2);
268    }
269
270    #[test]
271    fn command_result_to_query_result_indexes_record_sets() {
272        let res = command_result();
273        assert!(res.to_query_result(0).is_some());
274        assert!(res.to_query_result(1).is_some());
275        assert!(res.to_query_result(2).is_none());
276    }
277
278    #[test]
279    fn command_result_try_return_value_reads_named_out_param() {
280        let res = command_result();
281        let got: Option<i32> = res.try_return_value("@b").unwrap();
282        assert_eq!(got, Some(42));
283        assert!(res.try_return_value::<i32>("@missing").is_err());
284    }
285
286    #[test]
287    fn command_result_into_iter_yields_each_record_set() {
288        assert_eq!(command_result().into_iter().count(), 2);
289    }
290}