Skip to main content

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}