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