Skip to main content

sqlx_sqlserver/
value.rs

1use std::borrow::Cow;
2
3use sqlx_core::decode::Decode;
4use sqlx_core::encode::{Encode, IsNull};
5use sqlx_core::error::BoxDynError;
6use sqlx_core::types::Type;
7use sqlx_core::value::{Value, ValueRef};
8
9use crate::decimal_tools::{decode_money_bytes, decode_numeric_bytes};
10use crate::{Mssql, MssqlType, MssqlTypeInfo};
11
12/// Owned SQL Server value skeleton.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct MssqlValue {
15    type_info: MssqlTypeInfo,
16    data: Option<Vec<u8>>,
17}
18
19impl MssqlValue {
20    /// Creates an owned value from type information and raw little-endian TDS bytes.
21    pub(crate) fn new(type_info: MssqlTypeInfo, data: Option<Vec<u8>>) -> Self {
22        Self { type_info, data }
23    }
24
25    /// Creates a `NULL` value with the supplied type information.
26    pub fn null(type_info: MssqlTypeInfo) -> Self {
27        Self {
28            type_info,
29            data: None,
30        }
31    }
32}
33
34impl Value for MssqlValue {
35    type Database = Mssql;
36
37    fn as_ref(&self) -> MssqlValueRef<'_> {
38        MssqlValueRef {
39            type_info: &self.type_info,
40            data: self.data.as_deref(),
41        }
42    }
43
44    fn type_info(&self) -> Cow<'_, MssqlTypeInfo> {
45        Cow::Borrowed(&self.type_info)
46    }
47
48    fn is_null(&self) -> bool {
49        self.data.is_none()
50    }
51}
52
53/// Borrowed SQL Server value skeleton.
54#[derive(Debug, Clone, Copy)]
55pub struct MssqlValueRef<'r> {
56    type_info: &'r MssqlTypeInfo,
57    data: Option<&'r [u8]>,
58}
59
60impl<'r> ValueRef<'r> for MssqlValueRef<'r> {
61    type Database = Mssql;
62
63    fn to_owned(&self) -> MssqlValue {
64        MssqlValue {
65            type_info: self.type_info.clone(),
66            data: self.data.map(ToOwned::to_owned),
67        }
68    }
69
70    fn type_info(&self) -> Cow<'_, MssqlTypeInfo> {
71        Cow::Borrowed(self.type_info)
72    }
73
74    fn is_null(&self) -> bool {
75        self.data.is_none()
76    }
77}
78
79impl<'r> MssqlValueRef<'r> {
80    pub(crate) fn as_bytes(&self) -> Option<&'r [u8]> {
81        self.data
82    }
83
84    #[cfg(any(
85        feature = "bigdecimal",
86        feature = "chrono",
87        feature = "decimal",
88        feature = "json",
89        feature = "time",
90        feature = "uuid"
91    ))]
92    pub(crate) fn mssql_type_info(&self) -> &'r MssqlTypeInfo {
93        self.type_info
94    }
95}
96
97fn non_null_bytes<'r>(value: MssqlValueRef<'r>, rust_type: &str) -> Result<&'r [u8], BoxDynError> {
98    value
99        .as_bytes()
100        .ok_or_else(|| format!("cannot decode SQL Server NULL as {rust_type}").into())
101}
102
103fn decode_integer(value: MssqlValueRef<'_>, rust_type: &str) -> Result<i64, BoxDynError> {
104    let bytes = non_null_bytes(value, rust_type)?;
105
106    if matches!(value.type_info.kind(), MssqlType::Decimal) {
107        return decode_numeric_integer(bytes, value.type_info.scale());
108    }
109
110    match bytes.len() {
111        1 => Ok(i64::from(bytes[0])),
112        2 => Ok(i64::from(i16::from_le_bytes([bytes[0], bytes[1]]))),
113        4 => Ok(i64::from(i32::from_le_bytes([
114            bytes[0], bytes[1], bytes[2], bytes[3],
115        ]))),
116        8 => Ok(i64::from_le_bytes([
117            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
118        ])),
119        len => Err(format!("cannot decode {len}-byte SQL Server integer as {rust_type}").into()),
120    }
121}
122
123fn decode_numeric_integer(bytes: &[u8], mut scale: u8) -> Result<i64, BoxDynError> {
124    let (sign, mut numerator) = decode_numeric_bytes(bytes)?;
125
126    while numerator % 10 == 0 && scale > 0 {
127        numerator /= 10;
128        scale -= 1;
129    }
130
131    if scale > 0 {
132        numerator /= 10_u128.pow(u32::from(scale));
133    }
134
135    let value = i64::try_from(numerator)?;
136    Ok(value * i64::from(sign))
137}
138
139#[allow(clippy::cast_precision_loss)]
140fn decode_numeric_f64(bytes: &[u8], mut scale: u8) -> Result<f64, BoxDynError> {
141    let (sign, mut numerator) = decode_numeric_bytes(bytes)?;
142
143    while numerator % 10 == 0 && scale > 0 {
144        numerator /= 10;
145        scale -= 1;
146    }
147
148    let denominator = 10_u128.pow(u32::from(scale));
149    let integer_part = (numerator / denominator) as f64;
150    let fractional_part = (numerator % denominator) as f64 / denominator as f64;
151    let absolute = integer_part + fractional_part;
152
153    Ok(if sign == 1 { absolute } else { -absolute })
154}
155
156#[allow(clippy::cast_precision_loss)]
157fn decode_money_f64(bytes: &[u8]) -> Result<f64, BoxDynError> {
158    let numerator = decode_money_bytes(bytes)?;
159    let denominator = 10_000;
160    let integer_part = (numerator / denominator) as f64;
161    let fractional_part = (numerator % denominator) as f64 / denominator as f64;
162
163    Ok(integer_part + fractional_part)
164}
165
166fn is_integer_compatible(ty: &MssqlTypeInfo) -> bool {
167    matches!(
168        ty.kind(),
169        MssqlType::TinyInt
170            | MssqlType::SmallInt
171            | MssqlType::Int
172            | MssqlType::BigInt
173            | MssqlType::Decimal
174    )
175}
176
177impl Type<Mssql> for i8 {
178    fn type_info() -> MssqlTypeInfo {
179        MssqlTypeInfo::SMALLINT
180    }
181
182    fn compatible(ty: &MssqlTypeInfo) -> bool {
183        is_integer_compatible(ty)
184    }
185}
186
187impl Encode<'_, Mssql> for i8 {
188    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
189        <i16 as Encode<Mssql>>::encode_by_ref(&i16::from(*self), buf)
190    }
191}
192
193impl Decode<'_, Mssql> for i8 {
194    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
195        Ok(i8::try_from(decode_integer(value, "i8")?)?)
196    }
197}
198
199impl Type<Mssql> for u8 {
200    fn type_info() -> MssqlTypeInfo {
201        MssqlTypeInfo::TINYINT
202    }
203
204    fn compatible(ty: &MssqlTypeInfo) -> bool {
205        is_integer_compatible(ty)
206    }
207}
208
209impl Encode<'_, Mssql> for u8 {
210    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
211        buf.push(*self);
212        Ok(IsNull::No)
213    }
214}
215
216impl Decode<'_, Mssql> for u8 {
217    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
218        Ok(u8::try_from(decode_integer(value, "u8")?)?)
219    }
220}
221
222impl Type<Mssql> for i32 {
223    fn type_info() -> MssqlTypeInfo {
224        MssqlTypeInfo::INT
225    }
226
227    fn compatible(ty: &MssqlTypeInfo) -> bool {
228        is_integer_compatible(ty)
229    }
230}
231
232impl Encode<'_, Mssql> for i32 {
233    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
234        buf.extend_from_slice(&self.to_le_bytes());
235        Ok(IsNull::No)
236    }
237}
238
239impl Type<Mssql> for bool {
240    fn type_info() -> MssqlTypeInfo {
241        MssqlTypeInfo::BIT
242    }
243
244    fn compatible(ty: &MssqlTypeInfo) -> bool {
245        matches!(ty.kind(), MssqlType::Bit)
246    }
247}
248
249impl Encode<'_, Mssql> for bool {
250    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
251        buf.push(u8::from(*self));
252        Ok(IsNull::No)
253    }
254}
255
256impl Decode<'_, Mssql> for bool {
257    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
258        let bytes = value
259            .as_bytes()
260            .ok_or_else(|| "cannot decode SQL Server NULL as bool".to_owned())?;
261
262        match bytes {
263            [0] => Ok(false),
264            [1] => Ok(true),
265            _ => Err("cannot decode SQL Server bit as bool".into()),
266        }
267    }
268}
269
270impl Type<Mssql> for i16 {
271    fn type_info() -> MssqlTypeInfo {
272        MssqlTypeInfo::SMALLINT
273    }
274
275    fn compatible(ty: &MssqlTypeInfo) -> bool {
276        is_integer_compatible(ty)
277    }
278}
279
280impl Encode<'_, Mssql> for i16 {
281    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
282        buf.extend_from_slice(&self.to_le_bytes());
283        Ok(IsNull::No)
284    }
285}
286
287impl Decode<'_, Mssql> for i16 {
288    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
289        Ok(i16::try_from(decode_integer(value, "i16")?)?)
290    }
291}
292
293impl Type<Mssql> for u16 {
294    fn type_info() -> MssqlTypeInfo {
295        MssqlTypeInfo::INT
296    }
297
298    fn compatible(ty: &MssqlTypeInfo) -> bool {
299        is_integer_compatible(ty)
300    }
301}
302
303impl Encode<'_, Mssql> for u16 {
304    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
305        <i32 as Encode<Mssql>>::encode_by_ref(&i32::from(*self), buf)
306    }
307}
308
309impl Decode<'_, Mssql> for u16 {
310    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
311        Ok(u16::try_from(decode_integer(value, "u16")?)?)
312    }
313}
314
315impl Decode<'_, Mssql> for i32 {
316    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
317        Ok(i32::try_from(decode_integer(value, "i32")?)?)
318    }
319}
320
321impl Type<Mssql> for u32 {
322    fn type_info() -> MssqlTypeInfo {
323        MssqlTypeInfo::BIGINT
324    }
325
326    fn compatible(ty: &MssqlTypeInfo) -> bool {
327        is_integer_compatible(ty)
328    }
329}
330
331impl Encode<'_, Mssql> for u32 {
332    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
333        <i64 as Encode<Mssql>>::encode_by_ref(&i64::from(*self), buf)
334    }
335}
336
337impl Decode<'_, Mssql> for u32 {
338    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
339        Ok(u32::try_from(decode_integer(value, "u32")?)?)
340    }
341}
342
343impl Type<Mssql> for u64 {
344    fn type_info() -> MssqlTypeInfo {
345        MssqlTypeInfo::BIGINT
346    }
347
348    fn compatible(ty: &MssqlTypeInfo) -> bool {
349        is_integer_compatible(ty)
350    }
351}
352
353impl Encode<'_, Mssql> for u64 {
354    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
355        let value = i64::try_from(*self)?;
356        <i64 as Encode<Mssql>>::encode_by_ref(&value, buf)
357    }
358}
359
360impl Decode<'_, Mssql> for u64 {
361    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
362        Ok(u64::try_from(decode_integer(value, "u64")?)?)
363    }
364}
365
366impl Type<Mssql> for i64 {
367    fn type_info() -> MssqlTypeInfo {
368        MssqlTypeInfo::BIGINT
369    }
370
371    fn compatible(ty: &MssqlTypeInfo) -> bool {
372        is_integer_compatible(ty)
373    }
374}
375
376impl Encode<'_, Mssql> for i64 {
377    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
378        buf.extend_from_slice(&self.to_le_bytes());
379        Ok(IsNull::No)
380    }
381}
382
383impl Decode<'_, Mssql> for i64 {
384    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
385        decode_integer(value, "i64")
386    }
387}
388
389impl Type<Mssql> for f32 {
390    fn type_info() -> MssqlTypeInfo {
391        MssqlTypeInfo::REAL
392    }
393
394    fn compatible(ty: &MssqlTypeInfo) -> bool {
395        <f64 as Type<Mssql>>::compatible(ty)
396    }
397}
398
399impl Encode<'_, Mssql> for f32 {
400    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
401        buf.extend_from_slice(&self.to_le_bytes());
402        Ok(IsNull::No)
403    }
404}
405
406impl Decode<'_, Mssql> for f32 {
407    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
408        if !matches!(value.type_info.kind(), MssqlType::Real) {
409            return Ok(<f64 as Decode<Mssql>>::decode(value)? as f32);
410        }
411
412        let bytes = value
413            .as_bytes()
414            .ok_or_else(|| "cannot decode SQL Server NULL as f32".to_owned())?;
415
416        match bytes {
417            [a, b, c, d] => Ok(f32::from_le_bytes([*a, *b, *c, *d])),
418            _ => Err("cannot decode SQL Server real as f32".into()),
419        }
420    }
421}
422
423impl Type<Mssql> for f64 {
424    fn type_info() -> MssqlTypeInfo {
425        MssqlTypeInfo::FLOAT
426    }
427
428    fn compatible(ty: &MssqlTypeInfo) -> bool {
429        matches!(
430            ty.kind(),
431            MssqlType::Real | MssqlType::Float | MssqlType::Decimal | MssqlType::Money
432        )
433    }
434}
435
436impl Decode<'_, Mssql> for f64 {
437    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
438        if matches!(value.type_info.kind(), MssqlType::Decimal) {
439            return decode_numeric_f64(non_null_bytes(value, "f64")?, value.type_info.scale());
440        }
441
442        if matches!(value.type_info.kind(), MssqlType::Money) {
443            return decode_money_f64(non_null_bytes(value, "f64")?);
444        }
445
446        match value
447            .as_bytes()
448            .ok_or_else(|| "cannot decode SQL Server NULL as f64".to_owned())?
449        {
450            [a, b, c, d] => Ok(f64::from(f32::from_le_bytes([*a, *b, *c, *d]))),
451            [a, b, c, d, e, f, g, h] => Ok(f64::from_le_bytes([*a, *b, *c, *d, *e, *f, *g, *h])),
452            _ => Err("cannot decode SQL Server float as f64".into()),
453        }
454    }
455}
456
457impl Encode<'_, Mssql> for f64 {
458    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
459        buf.extend_from_slice(&self.to_le_bytes());
460        Ok(IsNull::No)
461    }
462}
463
464impl Type<Mssql> for str {
465    fn type_info() -> MssqlTypeInfo {
466        MssqlTypeInfo::NVARCHAR
467    }
468
469    fn compatible(ty: &MssqlTypeInfo) -> bool {
470        matches!(ty.kind(), MssqlType::NVarChar | MssqlType::VarChar)
471    }
472}
473
474impl Type<Mssql> for String {
475    fn type_info() -> MssqlTypeInfo {
476        <str as Type<Mssql>>::type_info()
477    }
478
479    fn compatible(ty: &MssqlTypeInfo) -> bool {
480        <str as Type<Mssql>>::compatible(ty)
481    }
482}
483
484impl Encode<'_, Mssql> for str {
485    fn produces(&self) -> Option<MssqlTypeInfo> {
486        Some(MssqlTypeInfo::with_size(
487            MssqlType::NVarChar,
488            nvarchar_parameter_size(self),
489        ))
490    }
491
492    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
493        for unit in self.encode_utf16() {
494            buf.extend_from_slice(&unit.to_le_bytes());
495        }
496
497        Ok(IsNull::No)
498    }
499}
500
501impl<'q> Encode<'q, Mssql> for &'q str {
502    fn produces(&self) -> Option<MssqlTypeInfo> {
503        <str as Encode<Mssql>>::produces(*self)
504    }
505
506    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
507        <str as Encode<Mssql>>::encode_by_ref(*self, buf)
508    }
509}
510
511impl Encode<'_, Mssql> for String {
512    fn produces(&self) -> Option<MssqlTypeInfo> {
513        <str as Encode<Mssql>>::produces(self.as_str())
514    }
515
516    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
517        <str as Encode<Mssql>>::encode_by_ref(self.as_str(), buf)
518    }
519}
520
521impl Decode<'_, Mssql> for String {
522    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
523        let bytes = value
524            .as_bytes()
525            .ok_or_else(|| "cannot decode SQL Server NULL as String".to_owned())?;
526
527        if matches!(value.type_info.kind(), MssqlType::VarChar) {
528            return Ok(std::str::from_utf8(bytes)?.to_owned());
529        }
530
531        if bytes.len() % 2 != 0 {
532            return Err("cannot decode odd-length SQL Server UTF-16 text".into());
533        }
534
535        let units = bytes
536            .chunks_exact(2)
537            .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
538            .collect::<Vec<_>>();
539        Ok(String::from_utf16(&units)?)
540    }
541}
542
543impl Type<Mssql> for [u8] {
544    fn type_info() -> MssqlTypeInfo {
545        MssqlTypeInfo::VARBINARY
546    }
547
548    fn compatible(ty: &MssqlTypeInfo) -> bool {
549        matches!(ty.kind(), MssqlType::VarBinary)
550    }
551}
552
553impl Type<Mssql> for Vec<u8> {
554    fn type_info() -> MssqlTypeInfo {
555        <[u8] as Type<Mssql>>::type_info()
556    }
557
558    fn compatible(ty: &MssqlTypeInfo) -> bool {
559        <[u8] as Type<Mssql>>::compatible(ty)
560    }
561}
562
563impl Encode<'_, Mssql> for [u8] {
564    fn produces(&self) -> Option<MssqlTypeInfo> {
565        Some(MssqlTypeInfo::with_size(
566            MssqlType::VarBinary,
567            varbinary_parameter_size(self.len()),
568        ))
569    }
570
571    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
572        buf.extend_from_slice(self);
573        Ok(IsNull::No)
574    }
575}
576
577impl<'q> Encode<'q, Mssql> for &'q [u8] {
578    fn produces(&self) -> Option<MssqlTypeInfo> {
579        <[u8] as Encode<Mssql>>::produces(*self)
580    }
581
582    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
583        <[u8] as Encode<Mssql>>::encode_by_ref(*self, buf)
584    }
585}
586
587impl Encode<'_, Mssql> for Vec<u8> {
588    fn produces(&self) -> Option<MssqlTypeInfo> {
589        <[u8] as Encode<Mssql>>::produces(self.as_slice())
590    }
591
592    fn encode_by_ref(&self, buf: &mut Vec<u8>) -> Result<IsNull, BoxDynError> {
593        <[u8] as Encode<Mssql>>::encode_by_ref(self.as_slice(), buf)
594    }
595}
596
597impl Decode<'_, Mssql> for Vec<u8> {
598    fn decode(value: MssqlValueRef<'_>) -> Result<Self, BoxDynError> {
599        Ok(<&[u8] as Decode<Mssql>>::decode(value)?.to_vec())
600    }
601}
602
603impl<'r> Decode<'r, Mssql> for &'r [u8] {
604    fn decode(value: MssqlValueRef<'r>) -> Result<Self, BoxDynError> {
605        non_null_bytes(value, "bytes")
606    }
607}
608
609fn nvarchar_parameter_size(value: &str) -> u16 {
610    let bytes = value.encode_utf16().count().saturating_mul(2);
611    if bytes > 8000 {
612        u16::MAX
613    } else {
614        u16::try_from(std::cmp::max(2, bytes)).unwrap_or(u16::MAX)
615    }
616}
617
618fn varbinary_parameter_size(len: usize) -> u16 {
619    if len > 8000 {
620        u16::MAX
621    } else {
622        u16::try_from(std::cmp::max(1, len)).unwrap_or(u16::MAX)
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629
630    #[test]
631    fn integer_scalars_use_lossless_parameter_types() {
632        assert_eq!(MssqlTypeInfo::SMALLINT, <i8 as Type<Mssql>>::type_info());
633        assert_eq!(MssqlTypeInfo::TINYINT, <u8 as Type<Mssql>>::type_info());
634        assert_eq!(MssqlTypeInfo::INT, <u16 as Type<Mssql>>::type_info());
635        assert_eq!(MssqlTypeInfo::BIGINT, <u32 as Type<Mssql>>::type_info());
636    }
637
638    #[test]
639    fn encodes_unsigned_integer_scalars_without_saturation() {
640        let mut buf = Vec::new();
641        let _ = <u32 as Encode<Mssql>>::encode_by_ref(&u32::MAX, &mut buf).unwrap();
642        assert_eq!(i64::from(u32::MAX).to_le_bytes(), buf.as_slice());
643
644        buf.clear();
645        let _ = <u16 as Encode<Mssql>>::encode_by_ref(&u16::MAX, &mut buf).unwrap();
646        assert_eq!(i32::from(u16::MAX).to_le_bytes(), buf.as_slice());
647    }
648
649    #[test]
650    fn decodes_integer_scalars_with_range_checks() {
651        let value = MssqlValue::new(MssqlTypeInfo::INT, Some(65_535_i32.to_le_bytes().to_vec()));
652        assert_eq!(
653            65_535_u16,
654            <u16 as Decode<Mssql>>::decode(value.as_ref()).unwrap()
655        );
656
657        let negative = MssqlValue::new(MssqlTypeInfo::INT, Some((-1_i32).to_le_bytes().to_vec()));
658        assert!(<u16 as Decode<Mssql>>::decode(negative.as_ref()).is_err());
659
660        let too_large = MssqlValue::new(MssqlTypeInfo::INT, Some(128_i32.to_le_bytes().to_vec()));
661        assert!(<i8 as Decode<Mssql>>::decode(too_large.as_ref()).is_err());
662    }
663
664    #[test]
665    fn integer_scalars_are_compatible_with_decimal() {
666        let mut numeric = vec![1_u8];
667        numeric.extend_from_slice(&42_u128.to_le_bytes());
668        let value = MssqlValue::new(MssqlTypeInfo::DECIMAL, Some(numeric));
669
670        assert!(<i64 as Type<Mssql>>::compatible(&MssqlTypeInfo::DECIMAL));
671        assert_eq!(
672            42_i64,
673            <i64 as Decode<Mssql>>::decode(value.as_ref()).unwrap()
674        );
675    }
676
677    #[test]
678    fn decodes_borrowed_bytes() {
679        let value = MssqlValue::new(MssqlTypeInfo::VARBINARY, Some(vec![1, 2, 3, 4]));
680        let bytes = <&[u8] as Decode<Mssql>>::decode(value.as_ref()).unwrap();
681
682        assert_eq!(&[1, 2, 3, 4], bytes);
683    }
684
685    #[test]
686    fn decodes_ascii_varchar_as_utf8() {
687        let value = MssqlValue::new(MssqlTypeInfo::VARCHAR, Some(b"hello".to_vec()));
688
689        assert_eq!(
690            "hello",
691            <String as Decode<Mssql>>::decode(value.as_ref()).unwrap()
692        );
693    }
694
695    #[test]
696    fn decodes_nvarchar_as_utf16() {
697        let mut data = Vec::new();
698        for unit in "hello".encode_utf16() {
699            data.extend_from_slice(&unit.to_le_bytes());
700        }
701
702        let value = MssqlValue::new(MssqlTypeInfo::NVARCHAR, Some(data));
703
704        assert_eq!(
705            "hello",
706            <String as Decode<Mssql>>::decode(value.as_ref()).unwrap()
707        );
708    }
709}