1#![allow(clippy::expect_used)]
8
9use bytes::{Buf, Bytes};
10
11use crate::error::TypeError;
12use crate::value::SqlValue;
13
14pub trait TdsDecode: Sized {
16 fn decode(buf: &mut Bytes, type_info: &TypeInfo) -> Result<Self, TypeError>;
18}
19
20#[derive(Debug, Clone)]
22pub struct TypeInfo {
23 pub type_id: u8,
25 pub length: Option<u32>,
27 pub scale: Option<u8>,
29 pub precision: Option<u8>,
31 pub collation: Option<Collation>,
33}
34
35#[derive(Debug, Clone, Copy)]
37pub struct Collation {
38 pub lcid: u32,
40 pub flags: u8,
42}
43
44impl Collation {
45 #[must_use]
50 pub fn is_utf8(&self) -> bool {
51 (self.lcid & 0x0400_0000) != 0
52 }
53
54 #[cfg(feature = "encoding")]
59 #[must_use]
60 pub fn encoding(&self) -> Option<&'static encoding_rs::Encoding> {
61 encoding_for_lcid(self.lcid)
62 }
63}
64
65#[cfg(feature = "encoding")]
68const UTF8_COLLATION_FLAG: u32 = 0x0400_0000;
69
70#[cfg(feature = "encoding")]
72fn encoding_for_lcid(lcid: u32) -> Option<&'static encoding_rs::Encoding> {
73 if (lcid & UTF8_COLLATION_FLAG) != 0 {
75 return Some(encoding_rs::UTF_8);
76 }
77
78 let code_page = code_page_for_lcid(lcid)?;
80
81 match code_page {
83 874 => Some(encoding_rs::WINDOWS_874),
84 932 => Some(encoding_rs::SHIFT_JIS),
85 936 => Some(encoding_rs::GB18030),
86 949 => Some(encoding_rs::EUC_KR),
87 950 => Some(encoding_rs::BIG5),
88 1250 => Some(encoding_rs::WINDOWS_1250),
89 1251 => Some(encoding_rs::WINDOWS_1251),
90 1252 => Some(encoding_rs::WINDOWS_1252),
91 1253 => Some(encoding_rs::WINDOWS_1253),
92 1254 => Some(encoding_rs::WINDOWS_1254),
93 1255 => Some(encoding_rs::WINDOWS_1255),
94 1256 => Some(encoding_rs::WINDOWS_1256),
95 1257 => Some(encoding_rs::WINDOWS_1257),
96 1258 => Some(encoding_rs::WINDOWS_1258),
97 _ => None,
98 }
99}
100
101#[cfg(feature = "encoding")]
103fn code_page_for_lcid(lcid: u32) -> Option<u16> {
104 const PRIMARY_LANGUAGE_MASK: u32 = 0xFFFF;
109 let primary_lang = lcid & PRIMARY_LANGUAGE_MASK;
110
111 match primary_lang {
112 0x0411 => Some(932), 0x0804 | 0x1004 => Some(936), 0x0404 | 0x0C04 | 0x1404 => Some(950), 0x0412 => Some(949), 0x041E => Some(874), 0x042A => Some(1258), 0x0405 | 0x0415 | 0x040E | 0x041A | 0x081A | 0x141A | 0x101A | 0x041B | 0x0424 | 0x0418
121 | 0x041C => Some(1250),
122
123 0x0419 | 0x0422 | 0x0423 | 0x0402 | 0x042F | 0x0C1A | 0x201A | 0x0440 | 0x0843 | 0x0444
125 | 0x0450 | 0x0485 => Some(1251),
126
127 0x0408 => Some(1253), 0x041F | 0x042C => Some(1254), 0x040D => Some(1255), 0x0401 | 0x0801 | 0x0C01 | 0x1001 | 0x1401 | 0x1801 | 0x1C01 | 0x2001 | 0x2401 | 0x2801
133 | 0x2C01 | 0x3001 | 0x3401 | 0x3801 | 0x3C01 | 0x4001 | 0x0429 | 0x0420 | 0x048C
134 | 0x0463 => Some(1256),
135
136 0x0425..=0x0427 => Some(1257),
138
139 0x0409 | 0x0809 | 0x0C09 | 0x1009 | 0x1409 | 0x1809 | 0x1C09 | 0x2009 | 0x2409 | 0x2809
141 | 0x2C09 | 0x3009 | 0x3409 | 0x0407 | 0x0807 | 0x0C07 | 0x1007 | 0x1407 | 0x040C
142 | 0x080C | 0x0C0C | 0x100C | 0x140C | 0x180C | 0x0410 | 0x0810 | 0x0413 | 0x0813
143 | 0x0416 | 0x0816 | 0x040A | 0x080A | 0x0C0A | 0x100A | 0x140A | 0x180A | 0x1C0A
144 | 0x200A | 0x240A | 0x280A | 0x2C0A | 0x300A | 0x340A | 0x380A | 0x3C0A | 0x400A
145 | 0x440A | 0x480A | 0x4C0A | 0x500A => Some(1252),
146
147 _ => Some(1252), }
149}
150
151impl TypeInfo {
152 #[must_use]
154 pub fn int(type_id: u8) -> Self {
155 Self {
156 type_id,
157 length: None,
158 scale: None,
159 precision: None,
160 collation: None,
161 }
162 }
163
164 #[must_use]
166 pub fn varchar(length: u32) -> Self {
167 Self {
168 type_id: 0xE7, length: Some(length),
170 scale: None,
171 precision: None,
172 collation: None,
173 }
174 }
175
176 #[must_use]
178 pub fn decimal(precision: u8, scale: u8) -> Self {
179 Self {
180 type_id: 0x6C,
181 length: None,
182 scale: Some(scale),
183 precision: Some(precision),
184 collation: None,
185 }
186 }
187
188 #[must_use]
190 pub fn datetime_with_scale(type_id: u8, scale: u8) -> Self {
191 Self {
192 type_id,
193 length: None,
194 scale: Some(scale),
195 precision: None,
196 collation: None,
197 }
198 }
199}
200
201pub(crate) mod sealed {
207 use super::*;
208
209 pub fn decode_decimal<B: Buf>(
216 buf: &mut B,
217 type_info: &TypeInfo,
218 ) -> Result<SqlValue, TypeError> {
219 super::decode_decimal(buf, type_info)
220 }
221
222 pub fn time_bytes_for_scale(scale: u8) -> usize {
230 super::time_bytes_for_scale(scale)
231 }
232
233 #[cfg(feature = "chrono")]
237 pub fn intervals_to_time(intervals: u64, scale: u8) -> chrono::NaiveTime {
238 super::intervals_to_time(intervals, scale)
239 }
240
241 pub fn decode_value(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
243 match type_info.type_id {
244 0x1F => Ok(SqlValue::Null), 0x32 => decode_bit(buf), 0x30 => decode_tinyint(buf), 0x34 => decode_smallint(buf), 0x38 => decode_int(buf), 0x7F => decode_bigint(buf), 0x3B => decode_float(buf), 0x3E => decode_double(buf), 0x26 => decode_intn(buf, type_info),
256
257 0xE7 => decode_nvarchar(buf, type_info), 0xAF => decode_varchar(buf, type_info), 0xA7 => decode_varchar(buf, type_info), 0xA5 => decode_varbinary(buf, type_info), 0xAD => decode_varbinary(buf, type_info), 0x24 => decode_guid(buf),
268
269 0x6C | 0x6A | 0x3F => decode_decimal(buf, type_info),
271
272 0x28 => decode_date(buf), 0x29 => decode_time(buf, type_info), 0x2A => decode_datetime2(buf, type_info), 0x2B => decode_datetimeoffset(buf, type_info), 0x3A => decode_smalldatetime(buf), 0x3D => decode_datetime(buf), 0xF1 => decode_xml(buf),
282
283 _ => Err(TypeError::UnsupportedConversion {
284 from: format!("TDS type 0x{:02X}", type_info.type_id),
285 to: "SqlValue",
286 }),
287 }
288 }
289}
290
291fn decode_bit(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
292 if buf.remaining() < 1 {
293 return Err(TypeError::BufferTooSmall {
294 needed: 1,
295 available: buf.remaining(),
296 });
297 }
298 Ok(SqlValue::Bool(buf.get_u8() != 0))
299}
300
301fn decode_tinyint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
302 if buf.remaining() < 1 {
303 return Err(TypeError::BufferTooSmall {
304 needed: 1,
305 available: buf.remaining(),
306 });
307 }
308 Ok(SqlValue::TinyInt(buf.get_u8()))
309}
310
311fn decode_smallint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
312 if buf.remaining() < 2 {
313 return Err(TypeError::BufferTooSmall {
314 needed: 2,
315 available: buf.remaining(),
316 });
317 }
318 Ok(SqlValue::SmallInt(buf.get_i16_le()))
319}
320
321fn decode_int(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
322 if buf.remaining() < 4 {
323 return Err(TypeError::BufferTooSmall {
324 needed: 4,
325 available: buf.remaining(),
326 });
327 }
328 Ok(SqlValue::Int(buf.get_i32_le()))
329}
330
331fn decode_bigint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
332 if buf.remaining() < 8 {
333 return Err(TypeError::BufferTooSmall {
334 needed: 8,
335 available: buf.remaining(),
336 });
337 }
338 Ok(SqlValue::BigInt(buf.get_i64_le()))
339}
340
341fn decode_float(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
342 if buf.remaining() < 4 {
343 return Err(TypeError::BufferTooSmall {
344 needed: 4,
345 available: buf.remaining(),
346 });
347 }
348 Ok(SqlValue::Float(buf.get_f32_le()))
349}
350
351fn decode_double(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
352 if buf.remaining() < 8 {
353 return Err(TypeError::BufferTooSmall {
354 needed: 8,
355 available: buf.remaining(),
356 });
357 }
358 Ok(SqlValue::Double(buf.get_f64_le()))
359}
360
361fn decode_intn(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
362 if buf.remaining() < 1 {
363 return Err(TypeError::BufferTooSmall {
364 needed: 1,
365 available: buf.remaining(),
366 });
367 }
368
369 let actual_len = buf.get_u8() as usize;
370 if actual_len == 0 {
371 return Ok(SqlValue::Null);
372 }
373
374 if buf.remaining() < actual_len {
375 return Err(TypeError::BufferTooSmall {
376 needed: actual_len,
377 available: buf.remaining(),
378 });
379 }
380
381 match actual_len {
382 1 => Ok(SqlValue::TinyInt(buf.get_u8())),
383 2 => Ok(SqlValue::SmallInt(buf.get_i16_le())),
384 4 => Ok(SqlValue::Int(buf.get_i32_le())),
385 8 => Ok(SqlValue::BigInt(buf.get_i64_le())),
386 _ => Err(TypeError::InvalidBinary(format!(
387 "invalid INTN length: {actual_len}"
388 ))),
389 }
390}
391
392fn decode_nvarchar(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
393 if buf.remaining() < 2 {
394 return Err(TypeError::BufferTooSmall {
395 needed: 2,
396 available: buf.remaining(),
397 });
398 }
399
400 let byte_len = buf.get_u16_le() as usize;
401
402 if byte_len == 0xFFFF {
404 return Ok(SqlValue::Null);
405 }
406
407 if buf.remaining() < byte_len {
408 return Err(TypeError::BufferTooSmall {
409 needed: byte_len,
410 available: buf.remaining(),
411 });
412 }
413
414 let utf16_data = buf.copy_to_bytes(byte_len);
415 let s = decode_utf16_string(&utf16_data)?;
416 Ok(SqlValue::String(s))
417}
418
419fn decode_varchar(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
420 if buf.remaining() < 2 {
421 return Err(TypeError::BufferTooSmall {
422 needed: 2,
423 available: buf.remaining(),
424 });
425 }
426
427 let byte_len = buf.get_u16_le() as usize;
428
429 if byte_len == 0xFFFF {
431 return Ok(SqlValue::Null);
432 }
433
434 if buf.remaining() < byte_len {
435 return Err(TypeError::BufferTooSmall {
436 needed: byte_len,
437 available: buf.remaining(),
438 });
439 }
440
441 let data = buf.copy_to_bytes(byte_len);
442
443 if let Ok(s) = String::from_utf8(data.to_vec()) {
445 return Ok(SqlValue::String(s));
446 }
447
448 #[cfg(feature = "encoding")]
450 if let Some(ref collation) = type_info.collation {
451 if let Some(encoding) = collation.encoding() {
452 let (decoded, _, had_errors) = encoding.decode(&data);
453 if !had_errors {
454 return Ok(SqlValue::String(decoded.into_owned()));
455 }
456 }
457 }
458
459 #[cfg(not(feature = "encoding"))]
461 let _ = type_info;
462
463 Ok(SqlValue::String(
465 String::from_utf8_lossy(&data).into_owned(),
466 ))
467}
468
469fn decode_varbinary(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
470 if buf.remaining() < 2 {
471 return Err(TypeError::BufferTooSmall {
472 needed: 2,
473 available: buf.remaining(),
474 });
475 }
476
477 let byte_len = buf.get_u16_le() as usize;
478
479 if byte_len == 0xFFFF {
481 return Ok(SqlValue::Null);
482 }
483
484 if buf.remaining() < byte_len {
485 return Err(TypeError::BufferTooSmall {
486 needed: byte_len,
487 available: buf.remaining(),
488 });
489 }
490
491 let data = buf.copy_to_bytes(byte_len);
492 Ok(SqlValue::Binary(data))
493}
494
495#[cfg(feature = "uuid")]
496fn decode_guid(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
497 if buf.remaining() < 1 {
498 return Err(TypeError::BufferTooSmall {
499 needed: 1,
500 available: buf.remaining(),
501 });
502 }
503
504 let len = buf.get_u8() as usize;
505 if len == 0 {
506 return Ok(SqlValue::Null);
507 }
508
509 if len != 16 {
510 return Err(TypeError::InvalidBinary(format!(
511 "invalid GUID length: {len}"
512 )));
513 }
514
515 if buf.remaining() < 16 {
516 return Err(TypeError::BufferTooSmall {
517 needed: 16,
518 available: buf.remaining(),
519 });
520 }
521
522 let mut bytes = [0u8; 16];
524
525 bytes[3] = buf.get_u8();
527 bytes[2] = buf.get_u8();
528 bytes[1] = buf.get_u8();
529 bytes[0] = buf.get_u8();
530
531 bytes[5] = buf.get_u8();
533 bytes[4] = buf.get_u8();
534
535 bytes[7] = buf.get_u8();
537 bytes[6] = buf.get_u8();
538
539 for byte in &mut bytes[8..16] {
541 *byte = buf.get_u8();
542 }
543
544 Ok(SqlValue::Uuid(uuid::Uuid::from_bytes(bytes)))
545}
546
547#[cfg(not(feature = "uuid"))]
548fn decode_guid(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
549 if buf.remaining() < 1 {
551 return Err(TypeError::BufferTooSmall {
552 needed: 1,
553 available: buf.remaining(),
554 });
555 }
556
557 let len = buf.get_u8() as usize;
558 if len == 0 {
559 return Ok(SqlValue::Null);
560 }
561
562 if buf.remaining() < len {
563 return Err(TypeError::BufferTooSmall {
564 needed: len,
565 available: buf.remaining(),
566 });
567 }
568
569 let data = buf.copy_to_bytes(len);
570 Ok(SqlValue::Binary(data))
571}
572
573#[cfg(feature = "decimal")]
574fn decode_decimal<B: Buf>(buf: &mut B, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
575 use rust_decimal::Decimal;
576
577 if buf.remaining() < 1 {
578 return Err(TypeError::BufferTooSmall {
579 needed: 1,
580 available: buf.remaining(),
581 });
582 }
583
584 let len = buf.get_u8() as usize;
585 if len == 0 {
586 return Ok(SqlValue::Null);
587 }
588
589 if buf.remaining() < len {
590 return Err(TypeError::BufferTooSmall {
591 needed: len,
592 available: buf.remaining(),
593 });
594 }
595
596 let sign = buf.get_u8();
598 let remaining = len - 1;
599
600 let mut mantissa_bytes = [0u8; 16];
602 for byte in mantissa_bytes.iter_mut().take(remaining.min(16)) {
603 *byte = buf.get_u8();
604 }
605 for _ in 16..remaining {
608 buf.get_u8();
609 }
610
611 let mantissa = u128::from_le_bytes(mantissa_bytes);
612 let scale = type_info.scale.unwrap_or(0) as u32;
613
614 let decimal = i128::try_from(mantissa)
621 .ok()
622 .and_then(|m| Decimal::try_from_i128_with_scale(m, scale).ok());
623 match decimal {
624 Some(mut decimal) => {
625 if sign == 0 {
626 decimal.set_sign_negative(true);
627 }
628 Ok(SqlValue::Decimal(decimal))
629 }
630 None => Err(TypeError::InvalidDecimal(format!(
631 "NUMERIC value (mantissa {mantissa}, scale {scale}) exceeds \
632 rust_decimal's 96-bit/scale-28 range; CAST the column to a \
633 narrower NUMERIC, FLOAT, or VARCHAR in the query"
634 ))),
635 }
636}
637
638#[cfg(not(feature = "decimal"))]
639fn decode_decimal<B: Buf>(buf: &mut B, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
640 if buf.remaining() < 1 {
642 return Err(TypeError::BufferTooSmall {
643 needed: 1,
644 available: buf.remaining(),
645 });
646 }
647
648 let len = buf.get_u8() as usize;
649 if len == 0 {
650 return Ok(SqlValue::Null);
651 }
652
653 if buf.remaining() < len {
654 return Err(TypeError::BufferTooSmall {
655 needed: len,
656 available: buf.remaining(),
657 });
658 }
659
660 buf.advance(len);
661 Ok(SqlValue::String("DECIMAL (feature disabled)".to_string()))
662}
663
664#[cfg(feature = "chrono")]
665fn decode_date(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
666 if buf.remaining() < 1 {
667 return Err(TypeError::BufferTooSmall {
668 needed: 1,
669 available: buf.remaining(),
670 });
671 }
672
673 let len = buf.get_u8() as usize;
674 if len == 0 {
675 return Ok(SqlValue::Null);
676 }
677
678 if len != 3 {
679 return Err(TypeError::InvalidDateTime(format!(
680 "invalid DATE length: {len}"
681 )));
682 }
683
684 if buf.remaining() < 3 {
685 return Err(TypeError::BufferTooSmall {
686 needed: 3,
687 available: buf.remaining(),
688 });
689 }
690
691 let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
693
694 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
695 let date = base + chrono::Duration::days(days as i64);
696
697 Ok(SqlValue::Date(date))
698}
699
700#[cfg(not(feature = "chrono"))]
701fn decode_date(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
702 if buf.remaining() < 1 {
703 return Err(TypeError::BufferTooSmall {
704 needed: 1,
705 available: buf.remaining(),
706 });
707 }
708
709 let len = buf.get_u8() as usize;
710 if len == 0 {
711 return Ok(SqlValue::Null);
712 }
713
714 if buf.remaining() < len {
715 return Err(TypeError::BufferTooSmall {
716 needed: len,
717 available: buf.remaining(),
718 });
719 }
720
721 buf.advance(len);
722 Ok(SqlValue::String("DATE (feature disabled)".to_string()))
723}
724
725#[cfg(feature = "chrono")]
726fn decode_time(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
727 let scale = type_info.scale.unwrap_or(7);
728 let time_len = time_bytes_for_scale(scale);
729
730 if buf.remaining() < 1 {
731 return Err(TypeError::BufferTooSmall {
732 needed: 1,
733 available: buf.remaining(),
734 });
735 }
736
737 let len = buf.get_u8() as usize;
738 if len == 0 {
739 return Ok(SqlValue::Null);
740 }
741
742 if buf.remaining() < len {
743 return Err(TypeError::BufferTooSmall {
744 needed: len,
745 available: buf.remaining(),
746 });
747 }
748 if len < time_len {
751 return Err(TypeError::InvalidDateTime(format!(
752 "TIME length {len} too short for scale {scale}"
753 )));
754 }
755
756 let mut time_bytes = [0u8; 8];
758 for byte in time_bytes.iter_mut().take(time_len) {
759 *byte = buf.get_u8();
760 }
761
762 let intervals = u64::from_le_bytes(time_bytes);
763 let time = intervals_to_time(intervals, scale);
764
765 Ok(SqlValue::Time(time))
766}
767
768#[cfg(not(feature = "chrono"))]
769fn decode_time(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
770 if buf.remaining() < 1 {
771 return Err(TypeError::BufferTooSmall {
772 needed: 1,
773 available: buf.remaining(),
774 });
775 }
776
777 let len = buf.get_u8() as usize;
778 if len == 0 {
779 return Ok(SqlValue::Null);
780 }
781
782 if buf.remaining() < len {
783 return Err(TypeError::BufferTooSmall {
784 needed: len,
785 available: buf.remaining(),
786 });
787 }
788
789 buf.advance(len);
790 Ok(SqlValue::String("TIME (feature disabled)".to_string()))
791}
792
793#[cfg(feature = "chrono")]
794fn decode_datetime2(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
795 let scale = type_info.scale.unwrap_or(7);
796 let time_len = time_bytes_for_scale(scale);
797
798 if buf.remaining() < 1 {
799 return Err(TypeError::BufferTooSmall {
800 needed: 1,
801 available: buf.remaining(),
802 });
803 }
804
805 let len = buf.get_u8() as usize;
806 if len == 0 {
807 return Ok(SqlValue::Null);
808 }
809
810 if buf.remaining() < len {
811 return Err(TypeError::BufferTooSmall {
812 needed: len,
813 available: buf.remaining(),
814 });
815 }
816 if len < time_len + 3 {
819 return Err(TypeError::InvalidDateTime(format!(
820 "DATETIME2 length {len} too short for scale {scale}"
821 )));
822 }
823
824 let mut time_bytes = [0u8; 8];
826 for byte in time_bytes.iter_mut().take(time_len) {
827 *byte = buf.get_u8();
828 }
829 let intervals = u64::from_le_bytes(time_bytes);
830 let time = intervals_to_time(intervals, scale);
831
832 let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
834 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
835 let date = base + chrono::Duration::days(days as i64);
836
837 Ok(SqlValue::DateTime(date.and_time(time)))
838}
839
840#[cfg(not(feature = "chrono"))]
841fn decode_datetime2(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
842 if buf.remaining() < 1 {
843 return Err(TypeError::BufferTooSmall {
844 needed: 1,
845 available: buf.remaining(),
846 });
847 }
848
849 let len = buf.get_u8() as usize;
850 if len == 0 {
851 return Ok(SqlValue::Null);
852 }
853
854 if buf.remaining() < len {
855 return Err(TypeError::BufferTooSmall {
856 needed: len,
857 available: buf.remaining(),
858 });
859 }
860
861 buf.advance(len);
862 Ok(SqlValue::String("DATETIME2 (feature disabled)".to_string()))
863}
864
865#[cfg(feature = "chrono")]
866fn decode_datetimeoffset(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
867 use chrono::TimeZone;
868
869 let scale = type_info.scale.unwrap_or(7);
870 let time_len = time_bytes_for_scale(scale);
871
872 if buf.remaining() < 1 {
873 return Err(TypeError::BufferTooSmall {
874 needed: 1,
875 available: buf.remaining(),
876 });
877 }
878
879 let len = buf.get_u8() as usize;
880 if len == 0 {
881 return Ok(SqlValue::Null);
882 }
883
884 if buf.remaining() < len {
885 return Err(TypeError::BufferTooSmall {
886 needed: len,
887 available: buf.remaining(),
888 });
889 }
890 if len < time_len + 5 {
893 return Err(TypeError::InvalidDateTime(format!(
894 "DATETIMEOFFSET length {len} too short for scale {scale}"
895 )));
896 }
897
898 let mut time_bytes = [0u8; 8];
900 for byte in time_bytes.iter_mut().take(time_len) {
901 *byte = buf.get_u8();
902 }
903 let intervals = u64::from_le_bytes(time_bytes);
904 let time = intervals_to_time(intervals, scale);
905
906 let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
908 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
909 let date = base + chrono::Duration::days(days as i64);
910
911 let offset_minutes = buf.get_i16_le();
913 let offset = chrono::FixedOffset::east_opt((offset_minutes as i32) * 60)
914 .ok_or_else(|| TypeError::InvalidDateTime(format!("invalid offset: {offset_minutes}")))?;
915
916 let datetime = offset.from_utc_datetime(&date.and_time(time));
919
920 Ok(SqlValue::DateTimeOffset(datetime))
921}
922
923#[cfg(not(feature = "chrono"))]
924fn decode_datetimeoffset(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
925 if buf.remaining() < 1 {
926 return Err(TypeError::BufferTooSmall {
927 needed: 1,
928 available: buf.remaining(),
929 });
930 }
931
932 let len = buf.get_u8() as usize;
933 if len == 0 {
934 return Ok(SqlValue::Null);
935 }
936
937 if buf.remaining() < len {
938 return Err(TypeError::BufferTooSmall {
939 needed: len,
940 available: buf.remaining(),
941 });
942 }
943
944 buf.advance(len);
945 Ok(SqlValue::String(
946 "DATETIMEOFFSET (feature disabled)".to_string(),
947 ))
948}
949
950#[cfg(feature = "chrono")]
951fn decode_datetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
952 if buf.remaining() < 8 {
954 return Err(TypeError::BufferTooSmall {
955 needed: 8,
956 available: buf.remaining(),
957 });
958 }
959
960 let days = buf.get_i32_le();
961 let time_300ths = buf.get_u32_le();
962
963 let base = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("valid date");
964 let date = base
966 .checked_add_signed(chrono::Duration::days(days as i64))
967 .ok_or_else(|| TypeError::InvalidDateTime(format!("DATETIME days out of range: {days}")))?;
968
969 let total_ms = (time_300ths as u64 * 1000) / 300;
971 let secs = (total_ms / 1000) as u32;
972 let nanos = ((total_ms % 1000) * 1_000_000) as u32;
973
974 let time = chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos)
975 .ok_or_else(|| TypeError::InvalidDateTime("invalid DATETIME time".to_string()))?;
976
977 Ok(SqlValue::DateTime(date.and_time(time)))
978}
979
980#[cfg(not(feature = "chrono"))]
981fn decode_datetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
982 if buf.remaining() < 8 {
983 return Err(TypeError::BufferTooSmall {
984 needed: 8,
985 available: buf.remaining(),
986 });
987 }
988
989 buf.advance(8);
990 Ok(SqlValue::String("DATETIME (feature disabled)".to_string()))
991}
992
993#[cfg(feature = "chrono")]
994fn decode_smalldatetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
995 if buf.remaining() < 4 {
997 return Err(TypeError::BufferTooSmall {
998 needed: 4,
999 available: buf.remaining(),
1000 });
1001 }
1002
1003 let days = buf.get_u16_le();
1004 let minutes = buf.get_u16_le();
1005
1006 let base = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("valid date");
1007 let date = base + chrono::Duration::days(days as i64);
1008
1009 let time = chrono::NaiveTime::from_num_seconds_from_midnight_opt((minutes as u32) * 60, 0)
1010 .ok_or_else(|| TypeError::InvalidDateTime("invalid SMALLDATETIME time".to_string()))?;
1011
1012 Ok(SqlValue::DateTime(date.and_time(time)))
1013}
1014
1015#[cfg(not(feature = "chrono"))]
1016fn decode_smalldatetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
1017 if buf.remaining() < 4 {
1018 return Err(TypeError::BufferTooSmall {
1019 needed: 4,
1020 available: buf.remaining(),
1021 });
1022 }
1023
1024 buf.advance(4);
1025 Ok(SqlValue::String(
1026 "SMALLDATETIME (feature disabled)".to_string(),
1027 ))
1028}
1029
1030fn decode_xml(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
1031 if buf.remaining() < 2 {
1033 return Err(TypeError::BufferTooSmall {
1034 needed: 2,
1035 available: buf.remaining(),
1036 });
1037 }
1038
1039 let byte_len = buf.get_u16_le() as usize;
1040
1041 if byte_len == 0xFFFF {
1042 return Ok(SqlValue::Null);
1043 }
1044
1045 if buf.remaining() < byte_len {
1046 return Err(TypeError::BufferTooSmall {
1047 needed: byte_len,
1048 available: buf.remaining(),
1049 });
1050 }
1051
1052 let utf16_data = buf.copy_to_bytes(byte_len);
1053 let s = decode_utf16_string(&utf16_data)?;
1054 Ok(SqlValue::Xml(s))
1055}
1056
1057pub fn decode_utf16_string(data: &[u8]) -> Result<String, TypeError> {
1059 if data.len() % 2 != 0 {
1060 return Err(TypeError::InvalidEncoding(
1061 "UTF-16 data must have even length".to_string(),
1062 ));
1063 }
1064
1065 let utf16: Vec<u16> = data
1066 .chunks_exact(2)
1067 .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
1068 .collect();
1069
1070 String::from_utf16(&utf16).map_err(|e| TypeError::InvalidEncoding(e.to_string()))
1071}
1072
1073fn time_bytes_for_scale(scale: u8) -> usize {
1075 match scale {
1076 0..=2 => 3,
1077 3..=4 => 4,
1078 5..=7 => 5,
1079 _ => 5, }
1081}
1082
1083#[cfg(feature = "chrono")]
1085fn intervals_to_time(intervals: u64, scale: u8) -> chrono::NaiveTime {
1086 let nanos = match scale {
1100 0 => intervals.saturating_mul(1_000_000_000),
1101 1 => intervals.saturating_mul(100_000_000),
1102 2 => intervals.saturating_mul(10_000_000),
1103 3 => intervals.saturating_mul(1_000_000),
1104 4 => intervals.saturating_mul(100_000),
1105 5 => intervals.saturating_mul(10_000),
1106 6 => intervals.saturating_mul(1_000),
1107 7 => intervals.saturating_mul(100),
1108 _ => intervals.saturating_mul(100),
1109 };
1110
1111 let secs = (nanos / 1_000_000_000) as u32;
1112 let nano_part = (nanos % 1_000_000_000) as u32;
1113
1114 chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, nano_part)
1115 .unwrap_or_else(|| chrono::NaiveTime::from_hms_opt(0, 0, 0).expect("valid time"))
1116}
1117
1118#[cfg(test)]
1119#[allow(clippy::unwrap_used, clippy::panic)]
1120mod tests {
1121 use super::sealed::decode_value;
1122 use super::*;
1123
1124 #[test]
1125 fn test_decode_int() {
1126 let mut buf = Bytes::from_static(&[42, 0, 0, 0]);
1127 let type_info = TypeInfo::int(0x38);
1128 let result = decode_value(&mut buf, &type_info).unwrap();
1129 assert_eq!(result, SqlValue::Int(42));
1130 }
1131
1132 #[cfg(feature = "decimal")]
1137 #[test]
1138 fn legacy_numeric_0x3f_decodes_as_decimal() {
1139 let mut buf = Bytes::from_static(&[0x03, 0x01, 0x39, 0x30]);
1141 let type_info = TypeInfo {
1142 type_id: 0x3F,
1143 length: None,
1144 scale: Some(2),
1145 precision: Some(5),
1146 collation: None,
1147 };
1148 let result = decode_value(&mut buf, &type_info).unwrap();
1149 assert_eq!(result, SqlValue::Decimal("123.45".parse().unwrap()));
1150 }
1151
1152 #[cfg(feature = "chrono")]
1155 #[test]
1156 fn smalldatetime_0x3a_decodes() {
1157 let mut buf = Bytes::from_static(&[100, 0, 0x58, 0x02]);
1159 let type_info = TypeInfo {
1160 type_id: 0x3A,
1161 length: None,
1162 scale: None,
1163 precision: None,
1164 collation: None,
1165 };
1166 let result = decode_value(&mut buf, &type_info).unwrap();
1167 let expected = chrono::NaiveDate::from_ymd_opt(1900, 1, 1)
1168 .unwrap()
1169 .checked_add_signed(chrono::Duration::days(100))
1170 .unwrap()
1171 .and_hms_opt(10, 0, 0)
1172 .unwrap();
1173 assert_eq!(result, SqlValue::DateTime(expected));
1174 }
1175
1176 #[cfg(feature = "encoding")]
1182 #[test]
1183 fn varchar_japanese_collation_decodes_cp932() {
1184 use bytes::BufMut;
1185 let (cp932, _, had_err) = encoding_rs::SHIFT_JIS.encode("あい");
1186 assert!(!had_err, "sample must be representable in cp932");
1187 let mut wire = bytes::BytesMut::new();
1188 wire.put_u16_le(cp932.len() as u16);
1189 wire.put_slice(&cp932);
1190 let type_info = TypeInfo {
1191 type_id: 0xA7, length: None,
1193 scale: None,
1194 precision: None,
1195 collation: Some(Collation {
1196 lcid: 13632529, flags: 0,
1198 }),
1199 };
1200 let result = decode_value(&mut wire.freeze(), &type_info).unwrap();
1201 assert_eq!(result, SqlValue::String("あい".to_string()));
1202 }
1203
1204 #[cfg(feature = "uuid")]
1209 #[test]
1210 fn test_guid_encode_decode_roundtrip() {
1211 use crate::encode::encode_uuid;
1212 use bytes::BufMut;
1213
1214 let original = uuid::Uuid::parse_str("00112233-4455-6677-8899-aabbccddeeff").unwrap();
1215 let mut encoded = bytes::BytesMut::new();
1216 encode_uuid(original, &mut encoded);
1217 assert_eq!(encoded.len(), 16);
1218
1219 let mut framed = bytes::BytesMut::new();
1221 framed.put_u8(16);
1222 framed.put_slice(&encoded);
1223 let decoded = decode_guid(&mut framed.freeze()).unwrap();
1224 assert_eq!(decoded, SqlValue::Uuid(original));
1225 }
1226
1227 #[cfg(feature = "chrono")]
1228 #[test]
1229 fn hostile_datetime_days_overflow_is_error_not_panic() {
1230 let mut data = Vec::new();
1233 data.extend_from_slice(&i32::MAX.to_le_bytes());
1234 data.extend_from_slice(&0u32.to_le_bytes());
1235 let mut buf = Bytes::from(data);
1236 assert!(decode_datetime(&mut buf).is_err());
1237 }
1238
1239 #[cfg(feature = "chrono")]
1246 #[test]
1247 fn test_datetimeoffset_decodes_wire_as_utc() {
1248 use chrono::TimeZone;
1249
1250 let mut data = Vec::new();
1251 data.push(10u8); let intervals: u64 = 10 * 3600 * 10_000_000; for i in 0..5 {
1254 data.push(((intervals >> (8 * i)) & 0xFF) as u8);
1255 }
1256 let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
1257 let days = (chrono::NaiveDate::from_ymd_opt(2024, 3, 15).unwrap() - base).num_days() as u32;
1258 data.push((days & 0xFF) as u8);
1259 data.push(((days >> 8) & 0xFF) as u8);
1260 data.push(((days >> 16) & 0xFF) as u8);
1261 data.extend_from_slice(&120i16.to_le_bytes()); let type_info = TypeInfo {
1264 type_id: 0x2B,
1265 length: None,
1266 scale: Some(7),
1267 precision: None,
1268 collation: None,
1269 };
1270 let mut buf = Bytes::from(data);
1271 let value = decode_datetimeoffset(&mut buf, &type_info).unwrap();
1272
1273 let offset = chrono::FixedOffset::east_opt(2 * 3600).unwrap();
1274 let expected = offset.with_ymd_and_hms(2024, 3, 15, 12, 0, 0).unwrap();
1275 match value {
1276 SqlValue::DateTimeOffset(dt) => {
1277 assert_eq!(dt, expected);
1278 assert_eq!(dt.offset().local_minus_utc(), 7200);
1279 assert_eq!(
1280 dt.naive_utc(),
1281 chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
1282 .unwrap()
1283 .and_hms_opt(10, 0, 0)
1284 .unwrap()
1285 );
1286 }
1287 other => panic!("expected DateTimeOffset, got {other:?}"),
1288 }
1289 }
1290
1291 #[test]
1292 fn test_decode_utf16_string() {
1293 let data = [0x41, 0x00, 0x42, 0x00];
1295 let result = decode_utf16_string(&data).unwrap();
1296 assert_eq!(result, "AB");
1297 }
1298
1299 #[test]
1300 fn test_decode_nvarchar() {
1301 let mut buf = Bytes::from_static(&[4, 0, 0x41, 0x00, 0x42, 0x00]);
1303 let type_info = TypeInfo::varchar(100);
1304 let type_info = TypeInfo {
1305 type_id: 0xE7,
1306 ..type_info
1307 };
1308 let result = decode_value(&mut buf, &type_info).unwrap();
1309 assert_eq!(result, SqlValue::String("AB".to_string()));
1310 }
1311
1312 #[test]
1313 fn test_decode_null_nvarchar() {
1314 let mut buf = Bytes::from_static(&[0xFF, 0xFF]);
1316 let type_info = TypeInfo {
1317 type_id: 0xE7,
1318 length: Some(100),
1319 scale: None,
1320 precision: None,
1321 collation: None,
1322 };
1323 let result = decode_value(&mut buf, &type_info).unwrap();
1324 assert_eq!(result, SqlValue::Null);
1325 }
1326
1327 #[cfg(feature = "decimal")]
1332 mod decimal_roundtrip {
1333 use super::*;
1334 use bytes::{BufMut, BytesMut};
1335 use rust_decimal::Decimal;
1336
1337 fn roundtrip_decimal(value: Decimal, precision: u8, scale: u8) -> Decimal {
1339 let mut encode_buf = BytesMut::new();
1341 crate::encode::encode_decimal(value, &mut encode_buf);
1342 let encoded_len = encode_buf.len() as u8; let mut decode_buf = BytesMut::with_capacity(1 + encoded_len as usize);
1346 decode_buf.put_u8(encoded_len);
1347 decode_buf.extend_from_slice(&encode_buf);
1348
1349 let mut bytes = decode_buf.freeze();
1350 let type_info = TypeInfo::decimal(precision, scale);
1351 match decode_value(&mut bytes, &type_info).unwrap() {
1352 SqlValue::Decimal(d) => d,
1353 other => panic!("expected Decimal, got {other:?}"),
1354 }
1355 }
1356
1357 #[test]
1358 fn test_negative_decimal_17_80() {
1359 let d = Decimal::new(-1780, 2); let result = roundtrip_decimal(d, 18, 2);
1361 assert_eq!(result, d, "round-trip of -17.80 must be exact");
1362 }
1363
1364 #[test]
1365 fn test_negative_decimal_0_01() {
1366 let d = Decimal::new(-1, 2); let result = roundtrip_decimal(d, 18, 2);
1368 assert_eq!(result, d, "round-trip of -0.01 must be exact");
1369 }
1370
1371 #[test]
1372 fn test_negative_decimal_large() {
1373 let d = Decimal::new(-9999999999, 2); let result = roundtrip_decimal(d, 18, 2);
1375 assert_eq!(result, d, "round-trip of -99999999.99 must be exact");
1376 }
1377
1378 #[test]
1379 fn test_positive_decimal() {
1380 let d = Decimal::new(1780, 2); let result = roundtrip_decimal(d, 18, 2);
1382 assert_eq!(result, d, "round-trip of 17.80 must be exact");
1383 }
1384
1385 #[test]
1386 fn test_decimal_zero() {
1387 let d = Decimal::ZERO;
1388 let result = roundtrip_decimal(d, 18, 0);
1389 assert_eq!(result, d, "round-trip of 0 must be exact");
1390 }
1391
1392 #[test]
1393 fn test_decimal_max_precision() {
1394 let d = Decimal::new(i64::MAX, 0);
1396 let result = roundtrip_decimal(d, 38, 0);
1397 assert_eq!(result, d, "round-trip of large positive must be exact");
1398 }
1399
1400 #[test]
1401 fn test_decimal_min_precision() {
1402 let d = Decimal::new(i64::MIN + 1, 0);
1403 let result = roundtrip_decimal(d, 38, 0);
1404 assert_eq!(result, d, "round-trip of large negative must be exact");
1405 }
1406
1407 #[test]
1411 fn test_decimal_out_of_range_errors_instead_of_f64() {
1412 let mut buf = BytesMut::new();
1414 buf.put_u8(17); buf.put_u8(1); buf.extend_from_slice(&[0xFF; 16]);
1417
1418 let mut bytes = buf.freeze();
1419 let type_info = TypeInfo::decimal(38, 0);
1420 match decode_value(&mut bytes, &type_info) {
1421 Err(TypeError::InvalidDecimal(_)) => {}
1422 other => panic!("expected InvalidDecimal error, got {other:?}"),
1423 }
1424 }
1425
1426 #[test]
1429 fn test_decimal_oversized_mantissa_keeps_frame_aligned() {
1430 let mut buf = BytesMut::new();
1431 buf.put_u8(18); buf.put_u8(1); buf.put_u8(42); buf.extend_from_slice(&[0u8; 16]); buf.put_u8(0xAB); let mut bytes = buf.freeze();
1438 let type_info = TypeInfo::decimal(38, 0);
1439 let value = decode_value(&mut bytes, &type_info).unwrap();
1440 assert_eq!(value, SqlValue::Decimal(Decimal::new(42, 0)));
1441 assert_eq!(
1442 bytes.remaining(),
1443 1,
1444 "excess mantissa bytes must be consumed, leaving the sentinel"
1445 );
1446 assert_eq!(bytes.get_u8(), 0xAB);
1447 }
1448 }
1449
1450 #[cfg(feature = "chrono")]
1455 mod date_tests {
1456 use bytes::{BufMut, BytesMut};
1457 use chrono::NaiveDate;
1458
1459 #[test]
1460 fn test_encode_date_pre_1900() {
1461 let mut buf = BytesMut::new();
1463 let date = NaiveDate::from_ymd_opt(1753, 1, 1).unwrap();
1464 crate::encode::encode_date(date, &mut buf).unwrap();
1465 assert_eq!(buf.len(), 3, "DATE encoding is always 3 bytes");
1466 }
1467
1468 #[test]
1469 fn test_encode_date_epoch() {
1470 let mut buf = BytesMut::new();
1472 let date = NaiveDate::from_ymd_opt(1, 1, 1).unwrap();
1473 crate::encode::encode_date(date, &mut buf).unwrap();
1474 assert_eq!(&buf[..], &[0, 0, 0]);
1476 }
1477
1478 #[test]
1479 fn test_encode_date_max() {
1480 let mut buf = BytesMut::new();
1482 let date = NaiveDate::from_ymd_opt(9999, 12, 31).unwrap();
1483 crate::encode::encode_date(date, &mut buf).unwrap();
1484 assert_eq!(buf.len(), 3, "DATE encoding is always 3 bytes");
1485 let days = buf[0] as u32 | ((buf[1] as u32) << 8) | ((buf[2] as u32) << 16);
1487 assert_eq!(days, 3_652_058);
1488 }
1489
1490 #[test]
1491 fn test_decode_datetime_pre_1900() {
1492 use super::*;
1495
1496 let base = NaiveDate::from_ymd_opt(1900, 1, 1).unwrap();
1497 let target = NaiveDate::from_ymd_opt(1753, 1, 1).unwrap();
1498 let days = target.signed_duration_since(base).num_days() as i32;
1499
1500 let mut raw = BytesMut::new();
1502 raw.put_i32_le(days);
1503 raw.put_u32_le(0); let mut buf = raw.freeze();
1506 let result = decode_datetime(&mut buf).unwrap();
1507
1508 match result {
1509 SqlValue::DateTime(dt) => {
1510 assert_eq!(dt.date(), target);
1511 }
1512 other => panic!("expected DateTime, got {other:?}"),
1513 }
1514 }
1515
1516 #[test]
1517 fn test_decode_smalldatetime_1900() {
1518 use super::*;
1520
1521 let mut raw = BytesMut::new();
1523 raw.put_u16_le(0);
1524 raw.put_u16_le(0);
1525
1526 let mut buf = raw.freeze();
1527 let result = decode_smalldatetime(&mut buf).unwrap();
1528
1529 match result {
1530 SqlValue::DateTime(dt) => {
1531 assert_eq!(
1532 dt,
1533 NaiveDate::from_ymd_opt(1900, 1, 1)
1534 .unwrap()
1535 .and_hms_opt(0, 0, 0)
1536 .unwrap()
1537 );
1538 }
1539 other => panic!("expected DateTime, got {other:?}"),
1540 }
1541 }
1542 }
1543
1544 #[cfg(feature = "decimal")]
1549 mod proptest_decimal {
1550 use super::*;
1551 use bytes::{BufMut, BytesMut};
1552 use proptest::prelude::*;
1553 use rust_decimal::Decimal;
1554
1555 fn roundtrip_decimal(value: Decimal, scale: u8) -> Decimal {
1557 let mut encode_buf = BytesMut::new();
1558 crate::encode::encode_decimal(value, &mut encode_buf);
1559 let encoded_len = encode_buf.len() as u8;
1560
1561 let mut decode_buf = BytesMut::with_capacity(1 + encoded_len as usize);
1562 decode_buf.put_u8(encoded_len);
1563 decode_buf.extend_from_slice(&encode_buf);
1564
1565 let mut bytes = decode_buf.freeze();
1566 let type_info = TypeInfo::decimal(38, scale);
1567 match decode_value(&mut bytes, &type_info).unwrap() {
1568 SqlValue::Decimal(d) => d,
1569 other => panic!("expected Decimal, got {other:?}"),
1570 }
1571 }
1572
1573 proptest! {
1574 #[test]
1575 fn decimal_roundtrip_scale0(mantissa in -999_999_999_999i64..=999_999_999_999i64) {
1576 let d = Decimal::new(mantissa, 0);
1577 let result = roundtrip_decimal(d, 0);
1578 prop_assert_eq!(result, d);
1579 }
1580
1581 #[test]
1582 fn decimal_roundtrip_scale2(mantissa in -999_999_999_999i64..=999_999_999_999i64) {
1583 let d = Decimal::new(mantissa, 2);
1584 let result = roundtrip_decimal(d, 2);
1585 prop_assert_eq!(result, d);
1586 }
1587
1588 #[test]
1589 fn decimal_roundtrip_various_scales(
1590 mantissa in -999_999_999i64..=999_999_999i64,
1591 scale in 0u8..=10u8,
1592 ) {
1593 let d = Decimal::new(mantissa, scale as u32);
1594 let result = roundtrip_decimal(d, scale);
1595 prop_assert_eq!(result, d);
1596 }
1597 }
1598 }
1599
1600 #[cfg(feature = "chrono")]
1601 mod proptest_date {
1602 use bytes::BytesMut;
1603 use chrono::NaiveDate;
1604 use proptest::prelude::*;
1605
1606 proptest! {
1607 #[test]
1608 fn date_encode_never_panics(
1609 year in 1i32..=9999i32,
1610 month in 1u32..=12u32,
1611 day in 1u32..=28u32, ) {
1613 let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
1614 let mut buf = BytesMut::new();
1615 crate::encode::encode_date(date, &mut buf).unwrap();
1616 prop_assert_eq!(buf.len(), 3);
1617 }
1618 }
1619 }
1620}