Skip to main content

tds_driver/
to_sql.rs

1use crate::{
2    tds::{codec::ColumnData, Numeric},
3    xml::XmlData,
4};
5use std::borrow::Cow;
6use uuid::Uuid;
7
8/// A conversion trait to a TDS type.
9///
10/// A `ToSql` implementation for a Rust type is needed for using it as a
11/// parameter in the [`Client#query`] or [`Client#execute`] methods. The
12/// following Rust types are already implemented to match the given server
13/// types:
14///
15/// |Rust type|Server type|
16/// |--------|--------|
17/// |`u8`|`tinyint`|
18/// |`i16`|`smallint`|
19/// |`i32`|`int`|
20/// |`i64`|`bigint`|
21/// |`f32`|`float(24)`|
22/// |`f64`|`float(53)`|
23/// |`bool`|`bit`|
24/// |`String`/`&str` (< 4000 characters)|`nvarchar(4000)`|
25/// |`String`/`&str`|`nvarchar(max)`|
26/// |`Vec<u8>`/`&[u8]` (< 8000 bytes)|`varbinary(8000)`|
27/// |`Vec<u8>`/`&[u8]`|`varbinary(max)`|
28/// |[`Uuid`]|`uniqueidentifier`|
29/// |[`Numeric`]|`numeric`/`decimal`|
30/// |[`Decimal`] (with feature flag `rust_decimal`)|`numeric`/`decimal`|
31/// |[`BigDecimal`] (with feature flag `bigdecimal`)|`numeric`/`decimal`|
32/// |[`XmlData`]|`xml`|
33/// |[`NaiveDate`] (with `chrono` feature, TDS 7.3 >)|`date`|
34/// |[`NaiveTime`] (with `chrono` feature, TDS 7.3 >)|`time`|
35/// |[`DateTime`] (with `chrono` feature, TDS 7.3 >)|`datetimeoffset`|
36/// |[`NaiveDateTime`] (with `chrono` feature, TDS 7.3 >)|`datetime2`|
37/// |[`NaiveDateTime`] (with `chrono` feature, TDS 7.2)|`datetime`|
38///
39/// It is possible to use some of the types to write into columns that are not
40/// of the same type. For example on systems following the TDS 7.3 standard (SQL
41/// Server 2008 and later), the chrono type `NaiveDateTime` can also be used to
42/// write to `datetime`, `datetime2` and `smalldatetime` columns. All string
43/// types can also be used with `ntext`, `text`, `varchar`, `nchar` and `char`
44/// columns. All binary types can also be used with `binary` and `image`
45/// columns.
46///
47/// See the [`time`] module for more information about the date and time structs.
48///
49/// A value the server cannot hold, such as a date outside 0001-01-01 to
50/// 9999-12-31 (in UTC, for `datetimeoffset`), a [`BigDecimal`] that
51/// `decimal(38, s)` cannot hold exactly, a chrono leap second or a chrono
52/// UTC offset with seconds, still converts, and the query using it fails
53/// with [`Error::Conversion`].
54///
55/// [`Error::Conversion`]: crate::error::Error::Conversion
56/// [`Client#query`]: struct.Client.html#method.query
57/// [`Client#execute`]: struct.Client.html#method.execute
58/// [`time`]: time/index.html
59/// [`Uuid`]: struct.Uuid.html
60/// [`Numeric`]: numeric/struct.Numeric.html
61/// [`Decimal`]: numeric/struct.Decimal.html
62/// [`BigDecimal`]: numeric/struct.BigDecimal.html
63/// [`XmlData`]: xml/struct.XmlData.html
64/// [`NaiveDateTime`]: time/chrono/struct.NaiveDateTime.html
65/// [`NaiveDate`]: time/chrono/struct.NaiveDate.html
66/// [`NaiveTime`]: time/chrono/struct.NaiveTime.html
67/// [`DateTime`]: time/chrono/struct.DateTime.html
68pub trait ToSql: Send + Sync {
69    /// Convert to a value understood by the SQL Server. Conversion
70    /// by-reference.
71    fn to_sql(&self) -> ColumnData<'_>;
72}
73
74/// A by-value conversion trait to a TDS type.
75pub trait IntoSql<'a>: Send + Sync {
76    /// Convert to a value understood by the SQL Server. Conversion by-value.
77    fn into_sql(self) -> ColumnData<'a>;
78}
79
80impl<'a> IntoSql<'a> for &'a str {
81    fn into_sql(self) -> ColumnData<'a> {
82        ColumnData::String(Some(Cow::Borrowed(self)))
83    }
84}
85
86impl<'a> IntoSql<'a> for Option<&'a str> {
87    fn into_sql(self) -> ColumnData<'a> {
88        ColumnData::String(self.map(Cow::Borrowed))
89    }
90}
91
92impl<'a> IntoSql<'a> for &'a String {
93    fn into_sql(self) -> ColumnData<'a> {
94        ColumnData::String(Some(Cow::Borrowed(self)))
95    }
96}
97
98impl<'a> IntoSql<'a> for Option<&'a String> {
99    fn into_sql(self) -> ColumnData<'a> {
100        ColumnData::String(self.map(Cow::from))
101    }
102}
103
104impl<'a> IntoSql<'a> for &'a [u8] {
105    fn into_sql(self) -> ColumnData<'a> {
106        ColumnData::Binary(Some(Cow::Borrowed(self)))
107    }
108}
109
110impl<'a> IntoSql<'a> for Option<&'a [u8]> {
111    fn into_sql(self) -> ColumnData<'a> {
112        ColumnData::Binary(self.map(Cow::Borrowed))
113    }
114}
115
116impl<'a> IntoSql<'a> for &'a Vec<u8> {
117    fn into_sql(self) -> ColumnData<'a> {
118        ColumnData::Binary(Some(Cow::from(self)))
119    }
120}
121
122impl<'a> IntoSql<'a> for Option<&'a Vec<u8>> {
123    fn into_sql(self) -> ColumnData<'a> {
124        ColumnData::Binary(self.map(Cow::from))
125    }
126}
127
128impl<'a> IntoSql<'a> for Cow<'a, str> {
129    fn into_sql(self) -> ColumnData<'a> {
130        ColumnData::String(Some(self))
131    }
132}
133
134impl<'a> IntoSql<'a> for Option<Cow<'a, str>> {
135    fn into_sql(self) -> ColumnData<'a> {
136        ColumnData::String(self)
137    }
138}
139
140impl<'a> IntoSql<'a> for Cow<'a, [u8]> {
141    fn into_sql(self) -> ColumnData<'a> {
142        ColumnData::Binary(Some(self))
143    }
144}
145
146impl<'a> IntoSql<'a> for Option<Cow<'a, [u8]>> {
147    fn into_sql(self) -> ColumnData<'a> {
148        ColumnData::Binary(self)
149    }
150}
151
152impl<'a> IntoSql<'a> for &'a XmlData {
153    fn into_sql(self) -> ColumnData<'a> {
154        ColumnData::Xml(Some(Cow::Borrowed(self)))
155    }
156}
157
158impl<'a> IntoSql<'a> for Option<&'a XmlData> {
159    fn into_sql(self) -> ColumnData<'a> {
160        ColumnData::Xml(self.map(Cow::Borrowed))
161    }
162}
163
164impl<'a> IntoSql<'a> for &'a Uuid {
165    fn into_sql(self) -> ColumnData<'a> {
166        ColumnData::Guid(Some(*self))
167    }
168}
169
170impl<'a> IntoSql<'a> for Option<&'a Uuid> {
171    fn into_sql(self) -> ColumnData<'a> {
172        ColumnData::Guid(self.copied())
173    }
174}
175
176into_sql!(self_,
177          String: (ColumnData::String, Cow::from(self_));
178          Vec<u8>: (ColumnData::Binary, Cow::from(self_));
179          Numeric: (ColumnData::Numeric, self_);
180          XmlData: (ColumnData::Xml, Cow::Owned(self_));
181          Uuid: (ColumnData::Guid, self_);
182          bool: (ColumnData::Bit, self_);
183          u8: (ColumnData::U8, self_);
184          i16: (ColumnData::I16, self_);
185          i32: (ColumnData::I32, self_);
186          i64: (ColumnData::I64, self_);
187          f32: (ColumnData::F32, self_);
188          f64: (ColumnData::F64, self_);
189);
190
191to_sql!(self_,
192        bool: (ColumnData::Bit, *self_);
193        u8: (ColumnData::U8, *self_);
194        i16: (ColumnData::I16, *self_);
195        i32: (ColumnData::I32, *self_);
196        i64: (ColumnData::I64, *self_);
197        f32: (ColumnData::F32, *self_);
198        f64: (ColumnData::F64, *self_);
199        &str: (ColumnData::String, Cow::from(*self_));
200        String: (ColumnData::String, Cow::from(self_));
201        Cow<'_, str>: (ColumnData::String, self_.clone());
202        &[u8]: (ColumnData::Binary, Cow::from(*self_));
203        Cow<'_, [u8]>: (ColumnData::Binary, self_.clone());
204        Vec<u8>: (ColumnData::Binary, Cow::from(self_));
205        Numeric: (ColumnData::Numeric, *self_);
206        XmlData: (ColumnData::Xml, Cow::Borrowed(self_));
207        Uuid: (ColumnData::Guid, *self_);
208);
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::tds::Numeric;
214    use crate::{IntoSql, ToSql};
215
216    #[test]
217    fn to_sql_scalars() {
218        assert_eq!(true.to_sql(), ColumnData::Bit(Some(true)));
219        assert_eq!(8u8.to_sql(), ColumnData::U8(Some(8)));
220        assert_eq!(16i16.to_sql(), ColumnData::I16(Some(16)));
221        assert_eq!(32i32.to_sql(), ColumnData::I32(Some(32)));
222        assert_eq!(64i64.to_sql(), ColumnData::I64(Some(64)));
223        assert_eq!(1.5f32.to_sql(), ColumnData::F32(Some(1.5)));
224        assert_eq!(2.5f64.to_sql(), ColumnData::F64(Some(2.5)));
225    }
226
227    #[test]
228    // The `&Some(..)`/`&None` borrows are intentional: they exercise the
229    // `ToSql for &T` impls, not the by-value ones, so the borrow is not needless.
230    #[allow(clippy::needless_borrow)]
231    fn to_sql_option_some_and_none() {
232        assert_eq!(Some(1i32).to_sql(), ColumnData::I32(Some(1)));
233        assert_eq!(None::<i32>.to_sql(), ColumnData::I32(None));
234        assert_eq!((&Some(1i32)).to_sql(), ColumnData::I32(Some(1)));
235        assert_eq!((&None::<i32>).to_sql(), ColumnData::I32(None));
236    }
237
238    #[test]
239    fn to_sql_strings_and_binary() {
240        assert_eq!("abc".to_sql(), ColumnData::String(Some(Cow::from("abc"))));
241        assert_eq!(
242            String::from("abc").to_sql(),
243            ColumnData::String(Some(Cow::from("abc")))
244        );
245        let v = vec![1u8, 2, 3];
246        assert_eq!(
247            v.to_sql(),
248            ColumnData::Binary(Some(Cow::from(vec![1, 2, 3])))
249        );
250        assert_eq!(
251            [1u8, 2, 3].as_slice().to_sql(),
252            ColumnData::Binary(Some(Cow::from(vec![1, 2, 3])))
253        );
254    }
255
256    #[test]
257    fn to_sql_numeric_and_uuid() {
258        let n = Numeric::new_with_scale(5, 1);
259        assert_eq!(n.to_sql(), ColumnData::Numeric(Some(n)));
260
261        let uuid = Uuid::nil();
262        assert_eq!(uuid.to_sql(), ColumnData::Guid(Some(uuid)));
263    }
264
265    #[test]
266    fn into_sql_borrowed_and_owned() {
267        assert_eq!(
268            "abc".into_sql(),
269            ColumnData::String(Some(Cow::Borrowed("abc")))
270        );
271        assert_eq!(
272            Some("abc").into_sql(),
273            ColumnData::String(Some(Cow::Borrowed("abc")))
274        );
275        assert_eq!(None::<&str>.into_sql(), ColumnData::String(None));
276
277        let bytes = vec![9u8, 8, 7];
278        assert_eq!(
279            bytes.as_slice().into_sql(),
280            ColumnData::Binary(Some(Cow::Borrowed(bytes.as_slice())))
281        );
282        assert_eq!(
283            (&bytes).into_sql(),
284            ColumnData::Binary(Some(Cow::from(&bytes)))
285        );
286
287        let uuid = Uuid::nil();
288        assert_eq!((&uuid).into_sql(), ColumnData::Guid(Some(uuid)));
289        assert_eq!(Some(&uuid).into_sql(), ColumnData::Guid(Some(uuid)));
290        assert_eq!(None::<&Uuid>.into_sql(), ColumnData::Guid(None));
291    }
292
293    #[test]
294    fn into_sql_scalars() {
295        assert_eq!(true.into_sql(), ColumnData::Bit(Some(true)));
296        assert_eq!(5i32.into_sql(), ColumnData::I32(Some(5)));
297        assert_eq!(None::<i32>.into_sql(), ColumnData::I32(None));
298        assert_eq!(
299            String::from("x").into_sql(),
300            ColumnData::String(Some(Cow::from("x")))
301        );
302    }
303
304    #[test]
305    fn into_sql_owned_string_and_ref() {
306        let owned = String::from("abc");
307        assert_eq!(
308            (&owned).into_sql(),
309            ColumnData::String(Some(Cow::from("abc")))
310        );
311        assert_eq!(
312            Some(&owned).into_sql(),
313            ColumnData::String(Some(Cow::from("abc")))
314        );
315        assert_eq!(None::<&String>.into_sql(), ColumnData::String(None));
316    }
317
318    #[test]
319    fn into_sql_binary_option_variants() {
320        assert_eq!(None::<&[u8]>.into_sql(), ColumnData::Binary(None));
321
322        let bytes = vec![1u8, 2, 3];
323        assert_eq!(
324            Some(bytes.as_slice()).into_sql(),
325            ColumnData::Binary(Some(Cow::from(bytes.as_slice())))
326        );
327        assert_eq!(None::<&Vec<u8>>.into_sql(), ColumnData::Binary(None));
328        assert_eq!(
329            bytes.into_sql(),
330            ColumnData::Binary(Some(Cow::from(vec![1, 2, 3])))
331        );
332    }
333
334    #[test]
335    fn into_sql_cow_variants() {
336        let cow_str: Cow<'_, str> = Cow::Borrowed("hi");
337        assert_eq!(
338            cow_str.into_sql(),
339            ColumnData::String(Some(Cow::from("hi")))
340        );
341        assert_eq!(
342            Some(Cow::Borrowed("hi")).into_sql(),
343            ColumnData::String(Some(Cow::from("hi")))
344        );
345        assert_eq!(None::<Cow<'_, str>>.into_sql(), ColumnData::String(None));
346
347        let cow_bin: Cow<'_, [u8]> = Cow::Borrowed(&[1u8, 2][..]);
348        assert_eq!(
349            cow_bin.into_sql(),
350            ColumnData::Binary(Some(Cow::from(vec![1u8, 2])))
351        );
352        assert_eq!(
353            Some(Cow::<[u8]>::Borrowed(&[1u8, 2][..])).into_sql(),
354            ColumnData::Binary(Some(Cow::from(vec![1u8, 2])))
355        );
356        assert_eq!(None::<Cow<'_, [u8]>>.into_sql(), ColumnData::Binary(None));
357    }
358
359    #[test]
360    fn into_sql_xml_and_numeric() {
361        let xml = XmlData::new("<a/>".to_string());
362        assert_eq!(
363            (&xml).into_sql(),
364            ColumnData::Xml(Some(Cow::Borrowed(&xml)))
365        );
366        assert_eq!(
367            Some(&xml).into_sql(),
368            ColumnData::Xml(Some(Cow::Borrowed(&xml)))
369        );
370        assert_eq!(None::<&XmlData>.into_sql(), ColumnData::Xml(None));
371
372        let xml_owned = XmlData::new("<b/>".to_string());
373        assert_eq!(
374            xml_owned.clone().into_sql(),
375            ColumnData::Xml(Some(Cow::Owned(xml_owned)))
376        );
377
378        let n = Numeric::new_with_scale(42, 0);
379        assert_eq!(n.into_sql(), ColumnData::Numeric(Some(n)));
380    }
381
382    #[test]
383    // The `&value` borrows are intentional: they exercise the `ToSql for &T`
384    // impls for the base scalar types, so the borrow is not needless.
385    #[allow(clippy::needless_borrow)]
386    fn to_sql_by_reference_scalars() {
387        // The macro-generated impls also cover `&T` for the base scalar types.
388        assert_eq!((&true).to_sql(), ColumnData::Bit(Some(true)));
389        assert_eq!((&8u8).to_sql(), ColumnData::U8(Some(8)));
390        assert_eq!((&16i16).to_sql(), ColumnData::I16(Some(16)));
391        assert_eq!((&64i64).to_sql(), ColumnData::I64(Some(64)));
392        assert_eq!((&1.5f32).to_sql(), ColumnData::F32(Some(1.5)));
393        assert_eq!((&2.5f64).to_sql(), ColumnData::F64(Some(2.5)));
394    }
395
396    #[test]
397    fn to_sql_cow_variants() {
398        let cow_str: Cow<'_, str> = Cow::Borrowed("hi");
399        assert_eq!(cow_str.to_sql(), ColumnData::String(Some(Cow::from("hi"))));
400
401        let cow_bin: Cow<'_, [u8]> = Cow::Borrowed(&[1u8, 2][..]);
402        assert_eq!(
403            cow_bin.to_sql(),
404            ColumnData::Binary(Some(Cow::from(vec![1u8, 2])))
405        );
406    }
407
408    #[test]
409    fn to_sql_xml() {
410        let xml = XmlData::new("<a/>".to_string());
411        assert_eq!(xml.to_sql(), ColumnData::Xml(Some(Cow::Borrowed(&xml))));
412    }
413}