tiberius/query.rs
1use std::borrow::Cow;
2
3use futures_util::io::{AsyncRead, AsyncWrite};
4
5use crate::{
6 tds::{codec::RpcProcId, stream::TokenStream},
7 Client, ColumnData, ExecuteResult, IntoSql, QueryStream,
8};
9
10/// A query object with bind parameters.
11#[derive(Debug)]
12pub struct Query<'a> {
13 sql: Cow<'a, str>,
14 params: Vec<ColumnData<'a>>,
15}
16
17impl<'a> Query<'a> {
18 /// Construct a new query object with the given SQL. If the SQL is
19 /// parameterized, the given number of parameters must be bound to the
20 /// object before executing.
21 ///
22 /// The `sql` can define the parameter placement by annotating them with
23 /// `@PN`, where N is the index of the parameter, starting from `1`.
24 pub fn new(sql: impl Into<Cow<'a, str>>) -> Self {
25 Self {
26 sql: sql.into(),
27 params: Vec::new(),
28 }
29 }
30
31 /// Bind a new parameter to the query. Must be called exactly as many times
32 /// as there are parameters in the given SQL. Otherwise the query will fail
33 /// on execution.
34 pub fn bind(&mut self, param: impl IntoSql<'a> + 'a) {
35 self.params.push(param.into_sql());
36 }
37
38 /// Bind every item of an iterator, in order.
39 ///
40 /// Equivalent to calling [`bind`] once per item. Pairs with
41 /// [`placeholders`] to build an `IN` list, where the number of
42 /// parameters is only known at runtime.
43 ///
44 /// # Example
45 ///
46 /// ```
47 /// # use tiberius::Query;
48 /// let ids = vec![1i32, 2, 3];
49 ///
50 /// let sql = format!(
51 /// "SELECT name FROM users WHERE id IN ({})",
52 /// Query::placeholders(1, ids.len()),
53 /// );
54 ///
55 /// let mut query = Query::new(sql);
56 /// query.bind_iter(ids);
57 ///
58 /// assert_eq!(query.param_count(), 3);
59 /// ```
60 ///
61 /// [`bind`]: #method.bind
62 /// [`placeholders`]: #method.placeholders
63 pub fn bind_iter(&mut self, params: impl IntoIterator<Item = impl IntoSql<'a> + 'a>) {
64 for param in params {
65 self.bind(param);
66 }
67 }
68
69 /// How many parameters have been bound so far.
70 ///
71 /// Useful for checking against [`MAX_PARAMETERS`] before executing a
72 /// statement whose parameter count is decided at runtime.
73 ///
74 /// [`MAX_PARAMETERS`]: #associatedconstant.MAX_PARAMETERS
75 pub fn param_count(&self) -> usize {
76 self.params.len()
77 }
78
79 /// The largest number of parameters SQL Server accepts in one statement.
80 ///
81 /// A statement carrying more is rejected by the server with
82 /// "The incoming request has too many parameters. The server supports a
83 /// maximum of 2100 parameters." — which arrives only after the whole
84 /// batch has been sent.
85 ///
86 /// This matters most for an `IN` list or a multi-row `INSERT`, where the
87 /// count comes from the length of a collection rather than from the SQL
88 /// text: the limit is reached by data volume, at run time, on a batch
89 /// that may be larger than any that was tested. Split such a batch into
90 /// chunks of at most `MAX_PARAMETERS / parameters_per_row` items.
91 ///
92 /// # Example
93 ///
94 /// ```
95 /// # use tiberius::Query;
96 /// // A three-column INSERT: three parameters per row.
97 /// let rows_per_statement = Query::MAX_PARAMETERS / 3;
98 /// assert_eq!(rows_per_statement, 700);
99 /// ```
100 pub const MAX_PARAMETERS: usize = 2100;
101
102 /// Build a `@P1, @P2, …` placeholder list for `count` parameters,
103 /// numbered from `first`.
104 ///
105 /// SQL Server has no array parameter, so an `IN` list must name one
106 /// placeholder per value, and `IN (@P1)` bound to a comma-separated
107 /// string matches nothing rather than failing. Generating the list is
108 /// the only way to write such a query, and this does it without a
109 /// format loop at every call site.
110 ///
111 /// `first` is 1-based, matching the `@P1` numbering
112 /// [`Query::new`] documents.
113 ///
114 /// # Example
115 ///
116 /// ```
117 /// # use tiberius::Query;
118 /// assert_eq!(Query::placeholders(1, 3), "@P1, @P2, @P3");
119 ///
120 /// // Continuing after parameters that are already bound.
121 /// assert_eq!(Query::placeholders(4, 2), "@P4, @P5");
122 /// ```
123 ///
124 /// A count of zero yields an empty string. `IN ()` is a syntax error, so
125 /// a caller with nothing to match on should skip the query rather than
126 /// build one:
127 ///
128 /// ```
129 /// # use tiberius::Query;
130 /// let ids: Vec<i32> = Vec::new();
131 /// assert!(Query::placeholders(1, ids.len()).is_empty());
132 /// ```
133 ///
134 /// [`Query::new`]: #method.new
135 pub fn placeholders(first: usize, count: usize) -> String {
136 use std::fmt::Write;
137
138 let mut out = String::with_capacity(count * 6);
139
140 for index in 0..count {
141 if index > 0 {
142 out.push_str(", ");
143 }
144 // Writing into a String cannot fail.
145 let _ = write!(out, "@P{}", first + index);
146 }
147
148 out
149 }
150
151 /// Executes SQL statements in the SQL Server, returning the number rows
152 /// affected. Useful for `INSERT`, `UPDATE` and `DELETE` statements. See
153 /// [`Client#execute`] for a simpler API if the parameters are statically
154 /// known.
155 ///
156 /// # Example
157 ///
158 /// ```no_run
159 /// # use tiberius::{Config, Query};
160 /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
161 /// # use std::env;
162 /// # #[tokio::main]
163 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
164 /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
165 /// # "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
166 /// # );
167 /// # let config = Config::from_ado_string(&c_str)?;
168 /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
169 /// # tcp.set_nodelay(true)?;
170 /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
171 /// let mut query = Query::new("INSERT INTO ##Test (id) VALUES (@P1), (@P2), (@P3)");
172 ///
173 /// query.bind("foo");
174 /// query.bind(2i32);
175 /// query.bind(String::from("bar"));
176 ///
177 /// let results = query.execute(&mut client).await?;
178 /// # Ok(())
179 /// # }
180 /// ```
181 ///
182 /// [`ToSql`]: trait.ToSql.html
183 /// [`FromSql`]: trait.FromSql.html
184 /// [`Client#execute`]: struct.Client.html#method.execute
185 pub async fn execute<S>(self, client: &mut Client<S>) -> crate::Result<ExecuteResult>
186 where
187 S: AsyncRead + AsyncWrite + Unpin + Send,
188 {
189 client.connection.flush_stream().await?;
190
191 let rpc_params = Client::<S>::rpc_params(self.sql);
192
193 client
194 .rpc_perform_query(RpcProcId::ExecuteSQL, rpc_params, self.params.into_iter())
195 .await?;
196
197 ExecuteResult::new(&mut client.connection).await
198 }
199
200 /// Executes SQL statements in the SQL Server, returning resulting rows.
201 /// Useful for `SELECT` statements. See [`Client#query`] for a simpler API
202 /// if the parameters are statically known.
203 ///
204 /// # Example
205 ///
206 /// ```
207 /// # use tiberius::{Config, Query};
208 /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
209 /// # use std::env;
210 /// # #[tokio::main]
211 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
212 /// # let c_str = env::var("TIBERIUS_TEST_CONNECTION_STRING").unwrap_or(
213 /// # "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
214 /// # );
215 /// # let config = Config::from_ado_string(&c_str)?;
216 /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
217 /// # tcp.set_nodelay(true)?;
218 /// # let mut client = tiberius::Client::connect(config, tcp.compat_write()).await?;
219 /// let mut query = Query::new("SELECT @P1, @P2, @P3");
220 ///
221 /// query.bind(1i32);
222 /// query.bind(2i32);
223 /// query.bind(3i32);
224 ///
225 /// let stream = query.query(&mut client).await?;
226 /// # Ok(())
227 /// # }
228 /// ```
229 ///
230 /// [`QueryStream`]: struct.QueryStream.html
231 /// [`ToSql`]: trait.ToSql.html
232 /// [`FromSql`]: trait.FromSql.html
233 /// [`Client#query`]: struct.Client.html#method.query
234 pub async fn query<'b, S>(self, client: &'b mut Client<S>) -> crate::Result<QueryStream<'b>>
235 where
236 S: AsyncRead + AsyncWrite + Unpin + Send,
237 {
238 client.connection.flush_stream().await?;
239 let rpc_params = Client::<S>::rpc_params(self.sql);
240
241 client
242 .rpc_perform_query(RpcProcId::ExecuteSQL, rpc_params, self.params.into_iter())
243 .await?;
244
245 let ts = TokenStream::new(&mut client.connection);
246 let mut result = QueryStream::new(ts.try_unfold());
247 result.forward_to_metadata().await?;
248
249 Ok(result)
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn placeholders_are_numbered_from_one() {
259 assert_eq!(Query::placeholders(1, 1), "@P1");
260 assert_eq!(Query::placeholders(1, 3), "@P1, @P2, @P3");
261 }
262
263 #[test]
264 fn placeholders_can_continue_from_an_offset() {
265 // For a query that already binds parameters before the list.
266 assert_eq!(Query::placeholders(4, 2), "@P4, @P5");
267 assert_eq!(Query::placeholders(10, 1), "@P10");
268 }
269
270 #[test]
271 fn no_placeholders_is_an_empty_string() {
272 // `IN ()` is a syntax error, so a caller with nothing to match on
273 // must skip the query rather than build one.
274 assert_eq!(Query::placeholders(1, 0), "");
275 assert_eq!(Query::placeholders(7, 0), "");
276 }
277
278 #[test]
279 fn placeholders_have_no_trailing_separator() {
280 let list = Query::placeholders(1, 5);
281 assert!(!list.ends_with(", "));
282 assert_eq!(list.matches(',').count(), 4);
283 }
284
285 #[test]
286 fn binding_an_iterator_counts_every_item() {
287 let mut query = Query::new("SELECT 1");
288 assert_eq!(query.param_count(), 0);
289
290 query.bind_iter(vec![1i32, 2, 3]);
291 assert_eq!(query.param_count(), 3);
292
293 query.bind(4i32);
294 assert_eq!(query.param_count(), 4);
295 }
296
297 #[test]
298 fn binding_an_empty_iterator_binds_nothing() {
299 let mut query = Query::new("SELECT 1");
300 query.bind_iter(Vec::<i32>::new());
301 assert_eq!(query.param_count(), 0);
302 }
303
304 #[test]
305 fn a_generated_list_matches_the_number_of_bound_parameters() {
306 // The invariant that makes this pair usable: one placeholder per
307 // bound value, or the server rejects the statement.
308 let ids = vec![10i32, 20, 30, 40];
309 let list = Query::placeholders(1, ids.len());
310
311 let mut query = Query::new(format!("SELECT * FROM t WHERE id IN ({list})"));
312 query.bind_iter(ids);
313
314 assert_eq!(list.matches("@P").count(), query.param_count());
315 }
316
317 #[test]
318 fn the_parameter_limit_is_the_documented_tds_maximum() {
319 assert_eq!(Query::MAX_PARAMETERS, 2100);
320 // The chunking arithmetic the docs describe.
321 assert_eq!(Query::MAX_PARAMETERS / 3, 700);
322 }
323}