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#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct MssqlValue {
15 type_info: MssqlTypeInfo,
16 data: Option<Vec<u8>>,
17}
18
19impl MssqlValue {
20 pub(crate) fn new(type_info: MssqlTypeInfo, data: Option<Vec<u8>>) -> Self {
22 Self { type_info, data }
23 }
24
25 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#[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}