Skip to main content

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