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}