Skip to main content

mssql_types/
decode.rs

1//! TDS binary decoding for SQL values.
2//!
3//! This module provides decoding of TDS wire format data into Rust values.
4
5// Allow expect() for chrono date construction with known-valid constant dates
6// (e.g., from_ymd_opt(1, 1, 1) for SQL Server epoch)
7#![allow(clippy::expect_used)]
8
9use bytes::{Buf, Bytes};
10
11use crate::error::TypeError;
12use crate::value::SqlValue;
13
14/// Trait for decoding values from TDS binary format.
15pub trait TdsDecode: Sized {
16    /// Decode a value from the buffer.
17    fn decode(buf: &mut Bytes, type_info: &TypeInfo) -> Result<Self, TypeError>;
18}
19
20/// TDS type information for decoding.
21#[derive(Debug, Clone)]
22pub struct TypeInfo {
23    /// The TDS type ID.
24    pub type_id: u8,
25    /// Length/precision for variable-length types.
26    pub length: Option<u32>,
27    /// Scale for decimal/time types.
28    pub scale: Option<u8>,
29    /// Precision for decimal types.
30    pub precision: Option<u8>,
31    /// Collation for string types.
32    pub collation: Option<Collation>,
33}
34
35/// SQL Server collation information.
36#[derive(Debug, Clone, Copy)]
37pub struct Collation {
38    /// Locale ID.
39    pub lcid: u32,
40    /// Collation flags.
41    pub flags: u8,
42}
43
44impl Collation {
45    /// Check if this collation uses UTF-8 encoding (SQL Server 2019+).
46    ///
47    /// UTF-8 collations have fUTF8, bit 26 (0x0400_0000), set in the
48    /// collation info field (bit 27 is FRESERVEDBIT per MS-TDS).
49    #[must_use]
50    pub fn is_utf8(&self) -> bool {
51        (self.lcid & 0x0400_0000) != 0
52    }
53
54    /// Get the encoding for this collation.
55    ///
56    /// Returns the appropriate `encoding_rs::Encoding` for the collation's LCID,
57    /// or `None` if the encoding is not supported.
58    #[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/// UTF-8 collation flag bit — fUTF8, bit 26 per the MS-TDS Collation Rule
66/// Definition (bit 27 is FRESERVEDBIT).
67#[cfg(feature = "encoding")]
68const UTF8_COLLATION_FLAG: u32 = 0x0400_0000;
69
70/// Get the encoding for an LCID value.
71#[cfg(feature = "encoding")]
72fn encoding_for_lcid(lcid: u32) -> Option<&'static encoding_rs::Encoding> {
73    // Check for UTF-8 collation first (SQL Server 2019+)
74    if (lcid & UTF8_COLLATION_FLAG) != 0 {
75        return Some(encoding_rs::UTF_8);
76    }
77
78    // Get code page from LCID
79    let code_page = code_page_for_lcid(lcid)?;
80
81    // Map code page to encoding
82    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/// Get the Windows code page for an LCID value.
102#[cfg(feature = "encoding")]
103fn code_page_for_lcid(lcid: u32) -> Option<u16> {
104    // Mask for the locale ID (lower 16 bits); the high bits carry sort/version
105    // flags, not the locale, so a 10-bit mask collapsed every full LCID and the
106    // arms below (full LCIDs like 0x0411) never matched. Mirrors
107    // `tds_protocol::collation::PRIMARY_LANGUAGE_MASK`.
108    const PRIMARY_LANGUAGE_MASK: u32 = 0xFFFF;
109    let primary_lang = lcid & PRIMARY_LANGUAGE_MASK;
110
111    match primary_lang {
112        0x0411 => Some(932),                   // Japanese - Shift_JIS
113        0x0804 | 0x1004 => Some(936),          // Chinese Simplified - GBK
114        0x0404 | 0x0C04 | 0x1404 => Some(950), // Chinese Traditional - Big5
115        0x0412 => Some(949),                   // Korean - EUC-KR
116        0x041E => Some(874),                   // Thai
117        0x042A => Some(1258),                  // Vietnamese
118
119        // Code Page 1250 - Central European
120        0x0405 | 0x0415 | 0x040E | 0x041A | 0x081A | 0x141A | 0x101A | 0x041B | 0x0424 | 0x0418
121        | 0x041C => Some(1250),
122
123        // Code Page 1251 - Cyrillic
124        0x0419 | 0x0422 | 0x0423 | 0x0402 | 0x042F | 0x0C1A | 0x201A | 0x0440 | 0x0843 | 0x0444
125        | 0x0450 | 0x0485 => Some(1251),
126
127        0x0408 => Some(1253),          // Greek
128        0x041F | 0x042C => Some(1254), // Turkish, Azerbaijani
129        0x040D => Some(1255),          // Hebrew
130
131        // Code Page 1256 - Arabic
132        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        // Code Page 1257 - Baltic
137        0x0425..=0x0427 => Some(1257),
138
139        // Default to 1252 (Western European) for English and related languages
140        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), // Default fallback
148    }
149}
150
151impl TypeInfo {
152    /// Create type info for a fixed-length integer type.
153    #[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    /// Create type info for a variable-length type.
165    #[must_use]
166    pub fn varchar(length: u32) -> Self {
167        Self {
168            type_id: 0xE7, // NVARCHARTYPE
169            length: Some(length),
170            scale: None,
171            precision: None,
172            collation: None,
173        }
174    }
175
176    /// Create type info for a decimal type.
177    #[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    /// Create type info for a datetime type with scale.
189    #[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
201/// Low-level TDS value decoder shared across the workspace crates.
202///
203/// Internal plumbing reached cross-crate only via [`crate::__private`]; not
204/// public API and exempt from semver guarantees (see #242). The per-type
205/// helpers it dispatches to remain module-private below.
206pub(crate) mod sealed {
207    use super::*;
208
209    /// Decode a DECIMAL/NUMERIC value (1-byte length prefix, sign byte,
210    /// little-endian mantissa).
211    ///
212    /// Shared with `mssql-client`'s `column_parser` so the two decode stacks
213    /// cannot drift on the overflow policy (the divergence that caused #188).
214    /// Generic over [`bytes::Buf`] so both `&[u8]` and [`Bytes`] callers reuse it.
215    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    /// Number of TDS wire bytes a scale-`scale` TIME/DATETIME2/DATETIMEOFFSET
223    /// time component occupies.
224    ///
225    /// Shared with `mssql-client`'s `column_parser` so the two decode stacks
226    /// agree on the scale→width mapping (see #204). Pure scale math, so it is
227    /// available without the `chrono` feature (column_parser validates frame
228    /// lengths against it even when `chrono` is off).
229    pub fn time_bytes_for_scale(scale: u8) -> usize {
230        super::time_bytes_for_scale(scale)
231    }
232
233    /// Convert wire 100ns-interval-derived `intervals` (at scale `scale`) into a
234    /// `NaiveTime`, shared with `column_parser` so the scale conversion cannot
235    /// drift (see #204).
236    #[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    /// Decode a SQL value based on type information.
242    pub fn decode_value(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
243        match type_info.type_id {
244            // Fixed-length types
245            0x1F => Ok(SqlValue::Null),   // NULLTYPE
246            0x32 => decode_bit(buf),      // BITTYPE
247            0x30 => decode_tinyint(buf),  // INT1TYPE
248            0x34 => decode_smallint(buf), // INT2TYPE
249            0x38 => decode_int(buf),      // INT4TYPE
250            0x7F => decode_bigint(buf),   // INT8TYPE
251            0x3B => decode_float(buf),    // FLT4TYPE
252            0x3E => decode_double(buf),   // FLT8TYPE
253
254            // Nullable integer types (INTNTYPE)
255            0x26 => decode_intn(buf, type_info),
256
257            // Variable-length string types
258            0xE7 => decode_nvarchar(buf, type_info), // NVARCHARTYPE
259            0xAF => decode_varchar(buf, type_info),  // BIGCHARTYPE
260            0xA7 => decode_varchar(buf, type_info),  // BIGVARCHARTYPE
261
262            // Binary types
263            0xA5 => decode_varbinary(buf, type_info), // BIGVARBINTYPE
264            0xAD => decode_varbinary(buf, type_info), // BIGBINARYTYPE
265
266            // GUID
267            0x24 => decode_guid(buf),
268
269            // Decimal/Numeric (0x6A/0x6C are the N-types; 0x3F is legacy NUMERICTYPE)
270            0x6C | 0x6A | 0x3F => decode_decimal(buf, type_info),
271
272            // Date/Time types
273            0x28 => decode_date(buf),                      // DATETYPE
274            0x29 => decode_time(buf, type_info),           // TIMETYPE
275            0x2A => decode_datetime2(buf, type_info),      // DATETIME2TYPE
276            0x2B => decode_datetimeoffset(buf, type_info), // DATETIMEOFFSETTYPE
277            0x3A => decode_smalldatetime(buf),             // DATETIM4TYPE (smalldatetime)
278            0x3D => decode_datetime(buf),                  // DATETIMETYPE
279
280            // XML
281            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    // 0xFFFF indicates NULL
403    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    // 0xFFFF indicates NULL
430    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    // Try UTF-8 first (most common case and zero-cost for ASCII)
444    if let Ok(s) = String::from_utf8(data.to_vec()) {
445        return Ok(SqlValue::String(s));
446    }
447
448    // If UTF-8 fails, try collation-aware decoding
449    #[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    // Suppress unused warning when encoding feature is disabled
460    #[cfg(not(feature = "encoding"))]
461    let _ = type_info;
462
463    // Fallback: lossy UTF-8 conversion
464    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    // 0xFFFF indicates NULL
480    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    // SQL Server stores UUIDs in mixed-endian format
523    let mut bytes = [0u8; 16];
524
525    // First 4 bytes - little-endian (reverse)
526    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    // Next 2 bytes - little-endian (reverse)
532    bytes[5] = buf.get_u8();
533    bytes[4] = buf.get_u8();
534
535    // Next 2 bytes - little-endian (reverse)
536    bytes[7] = buf.get_u8();
537    bytes[6] = buf.get_u8();
538
539    // Last 8 bytes - big-endian (keep as-is)
540    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    // Skip the GUID data
550    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    // First byte is sign (0 = negative, 1 = positive)
597    let sign = buf.get_u8();
598    let remaining = len - 1;
599
600    // Read mantissa (little-endian)
601    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    // Consume any excess bytes so the frame stays aligned (shouldn't happen
606    // with valid data; matches the column_parser decoder).
607    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    // rust_decimal holds 96-bit mantissas with scale <= 28; SQL Server
615    // NUMERIC goes to 38 digits, so legitimate wire values can exceed it.
616    // That must be an error, not a silent fall back to f64 (~15-16
617    // significant digits): a lossy value read, written back, or compared
618    // downstream corrupts data. This matches the column_parser decoder —
619    // the two policies diverged after #157 (issue #188).
620    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    // Skip decimal data and return as string
641    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    // 3 bytes little-endian representing days since 0001-01-01
692    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    // Reads below are driven by scale metadata, not by `len`: a short
749    // declared length must be an error, not a panic.
750    if len < time_len {
751        return Err(TypeError::InvalidDateTime(format!(
752            "TIME length {len} too short for scale {scale}"
753        )));
754    }
755
756    // Read time bytes (variable length based on scale)
757    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    // Reads below are driven by scale metadata, not by `len`: a short
817    // declared length must be an error, not a panic.
818    if len < time_len + 3 {
819        return Err(TypeError::InvalidDateTime(format!(
820            "DATETIME2 length {len} too short for scale {scale}"
821        )));
822    }
823
824    // Decode time
825    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    // Decode date
833    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    // Reads below are driven by scale metadata, not by `len`: a short
891    // declared length must be an error, not a panic.
892    if len < time_len + 5 {
893        return Err(TypeError::InvalidDateTime(format!(
894            "DATETIMEOFFSET length {len} too short for scale {scale}"
895        )));
896    }
897
898    // Decode time
899    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    // Decode date
907    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    // Decode timezone offset in minutes
912    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    // The wire date/time portion is UTC per MS-TDS §2.2.5.5.1.9; attach the
917    // offset without shifting the instant.
918    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    // DATETIME is 8 bytes: 4 bytes days since 1900-01-01 + 4 bytes 300ths of second
953    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    // days comes from the wire: out-of-range is an error, never a panic.
965    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    // Convert 300ths of second to time
970    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    // SMALLDATETIME is 4 bytes: 2 bytes days since 1900-01-01 + 2 bytes minutes
996    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    // XML is sent as UTF-16LE string with length prefix
1032    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
1057/// Decode a UTF-16LE string from bytes.
1058pub 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
1073/// Calculate number of bytes needed for TIME based on scale.
1074fn time_bytes_for_scale(scale: u8) -> usize {
1075    match scale {
1076        0..=2 => 3,
1077        3..=4 => 4,
1078        5..=7 => 5,
1079        _ => 5, // Default to max precision
1080    }
1081}
1082
1083/// Convert 100-nanosecond intervals to NaiveTime.
1084#[cfg(feature = "chrono")]
1085fn intervals_to_time(intervals: u64, scale: u8) -> chrono::NaiveTime {
1086    // Scale determines the unit:
1087    // scale 0: seconds
1088    // scale 1: 100ms
1089    // scale 2: 10ms
1090    // scale 3: 1ms
1091    // scale 4: 100us
1092    // scale 5: 10us
1093    // scale 6: 1us
1094    // scale 7: 100ns
1095
1096    // Saturating: `intervals` comes from the wire, and a hostile value must
1097    // not overflow-panic in debug builds (saturation lands in the
1098    // out-of-range fallback below).
1099    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    // ---- #204: secondary-decode-stack regressions ----
1133
1134    /// #204 bug 1: type 0x3F is legacy NUMERICTYPE, not SMALLDATETIME. It was
1135    /// wrongly dispatched to `decode_smalldatetime`; it must decode as a decimal.
1136    #[cfg(feature = "decimal")]
1137    #[test]
1138    fn legacy_numeric_0x3f_decodes_as_decimal() {
1139        // Decimal wire: [len][sign=1 (+)][magnitude LE]; 12345 = 0x3039, scale 2.
1140        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    /// #204 bug 1 (other half): 0x3A is DATETIM4 (SMALLDATETIME) and previously
1153    /// had no arm at all — the smalldatetime decoder was wired to 0x3F instead.
1154    #[cfg(feature = "chrono")]
1155    #[test]
1156    fn smalldatetime_0x3a_decodes() {
1157        // Raw 4 bytes: u16 days since 1900-01-01 + u16 minutes. 100 days, 600 min.
1158        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    /// #204 bug 2: the secondary stack masked the LCID to 10 bits before matching
1177    /// full 16-bit LCIDs, so every non-Latin collation collapsed to codepage 1252.
1178    /// A Japanese collation must decode cp932. Uses the real `Japanese_CI_AS` lcid
1179    /// (sort/version bits set: 0x00D00411) so the mask width genuinely matters —
1180    /// pre-fix this decoded the bytes as Windows-1252 garbage.
1181    #[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, // BIGVARCHARTYPE
1192            length: None,
1193            scale: None,
1194            precision: None,
1195            collation: Some(Collation {
1196                lcid: 13632529, // Japanese_CI_AS (0x00D00411)
1197                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    /// The mixed-endian GUID encoding must round-trip: encode then decode
1205    /// returns the original UUID. A UUID with all-distinct bytes catches an
1206    /// asymmetric swap. `decode_guid` previously had no unit coverage — only
1207    /// live round-trips exercised it.
1208    #[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        // `decode_guid` expects a length-prefixed buffer (1-byte length = 16).
1220        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        // days=i32::MAX from the wire overflows chrono's date range; must be
1231        // a TypeError, never a panic.
1232        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    /// Issue #152 regression: the DATETIMEOFFSET wire date/time portion is
1240    /// the UTC instant per MS-TDS §2.2.5.5.1.9; decoding must attach the
1241    /// offset without shifting it. Wire 10:00 UTC with +02:00 → 12:00+02:00.
1242    /// The previous from_local_datetime decode produced 10:00+02:00 (a
1243    /// different instant), which round-tripped only against our own equally
1244    /// inverted encoder.
1245    #[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); // BYTELEN: 5 time + 3 date + 2 offset
1252        let intervals: u64 = 10 * 3600 * 10_000_000; // 10:00:00 UTC, scale 7
1253        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()); // +02:00
1262
1263        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        // "AB" in UTF-16LE
1294        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        // Length (4 bytes for "AB") + "AB" in UTF-16LE
1302        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        // 0xFFFF indicates NULL
1315        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    // ========================================================================
1328    // Targeted round-trip tests for negative decimals (work-item 1.3)
1329    // ========================================================================
1330
1331    #[cfg(feature = "decimal")]
1332    mod decimal_roundtrip {
1333        use super::*;
1334        use bytes::{BufMut, BytesMut};
1335        use rust_decimal::Decimal;
1336
1337        /// Encode a Decimal, prepend TDS length byte, then decode — verifying round-trip.
1338        fn roundtrip_decimal(value: Decimal, precision: u8, scale: u8) -> Decimal {
1339            // Encode
1340            let mut encode_buf = BytesMut::new();
1341            crate::encode::encode_decimal(value, &mut encode_buf);
1342            let encoded_len = encode_buf.len() as u8; // 17 bytes (1 sign + 16 mantissa)
1343
1344            // Build decode buffer: length prefix + encoded data
1345            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); // -17.80
1360            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); // -0.01
1367            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); // -99999999.99
1374            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); // 17.80
1381            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            // Large value that fits in 38-digit precision
1395            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        /// Issue #188: a NUMERIC beyond rust_decimal's range must be a hard
1408        /// error, matching the column_parser policy from #157 — not a silent
1409        /// fall back to f64 (~15-16 significant digits of a 38-digit value).
1410        #[test]
1411        fn test_decimal_out_of_range_errors_instead_of_f64() {
1412            // 16 mantissa bytes of 0xFF = u128::MAX, far past 96 bits.
1413            let mut buf = BytesMut::new();
1414            buf.put_u8(17); // length: sign + 16 mantissa bytes
1415            buf.put_u8(1); // sign: positive
1416            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        /// Issue #188: mantissa bytes beyond the 16 we can hold must still be
1427        /// consumed, or every following column in the row misparses.
1428        #[test]
1429        fn test_decimal_oversized_mantissa_keeps_frame_aligned() {
1430            let mut buf = BytesMut::new();
1431            buf.put_u8(18); // length: sign + 17 mantissa bytes (one excess)
1432            buf.put_u8(1); // sign: positive
1433            buf.put_u8(42); // mantissa = 42
1434            buf.extend_from_slice(&[0u8; 16]); // 15 in-range zeros + 1 excess
1435            buf.put_u8(0xAB); // sentinel: the next value in the frame
1436
1437            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    // ========================================================================
1451    // Date encoding tests (work-item 3.7)
1452    // ========================================================================
1453
1454    #[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            // This is the scenario where Tiberius panics
1462            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            // The DATE epoch: 0001-01-01
1471            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            // Days since 0001-01-01 = 0
1475            assert_eq!(&buf[..], &[0, 0, 0]);
1476        }
1477
1478        #[test]
1479        fn test_encode_date_max() {
1480            // SQL Server max DATE: 9999-12-31
1481            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            // 3652058 days from 0001-01-01 — fits in 3 bytes (max ~16M)
1486            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            // DATETIME uses i32 days from 1900-01-01 epoch.
1493            // 1753-01-01 is ~53690 days before 1900-01-01.
1494            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            // Build DATETIME buffer: i32 days + u32 time_300ths
1501            let mut raw = BytesMut::new();
1502            raw.put_i32_le(days);
1503            raw.put_u32_le(0); // midnight
1504
1505            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            // SMALLDATETIME: u16 days from 1900-01-01 + u16 minutes
1519            use super::*;
1520
1521            // Day 0, minute 0 = 1900-01-01 00:00:00
1522            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    // ========================================================================
1545    // Property-based tests (work-item 5.2)
1546    // ========================================================================
1547
1548    #[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        /// Encode a Decimal, prepend TDS length byte, then decode.
1556        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, // 28 is always valid
1612            ) {
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}