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 bit 27 (0x0800_0000) set in the LCID.
48    #[must_use]
49    pub fn is_utf8(&self) -> bool {
50        (self.lcid & 0x0800_0000) != 0
51    }
52
53    /// Get the encoding for this collation.
54    ///
55    /// Returns the appropriate `encoding_rs::Encoding` for the collation's LCID,
56    /// or `None` if the encoding is not supported.
57    #[cfg(feature = "encoding")]
58    #[must_use]
59    pub fn encoding(&self) -> Option<&'static encoding_rs::Encoding> {
60        encoding_for_lcid(self.lcid)
61    }
62}
63
64/// UTF-8 collation flag bit (bit 27).
65#[cfg(feature = "encoding")]
66const UTF8_COLLATION_FLAG: u32 = 0x0800_0000;
67
68/// Get the encoding for an LCID value.
69#[cfg(feature = "encoding")]
70fn encoding_for_lcid(lcid: u32) -> Option<&'static encoding_rs::Encoding> {
71    // Check for UTF-8 collation first (SQL Server 2019+)
72    if (lcid & UTF8_COLLATION_FLAG) != 0 {
73        return Some(encoding_rs::UTF_8);
74    }
75
76    // Get code page from LCID
77    let code_page = code_page_for_lcid(lcid)?;
78
79    // Map code page to encoding
80    match code_page {
81        874 => Some(encoding_rs::WINDOWS_874),
82        932 => Some(encoding_rs::SHIFT_JIS),
83        936 => Some(encoding_rs::GB18030),
84        949 => Some(encoding_rs::EUC_KR),
85        950 => Some(encoding_rs::BIG5),
86        1250 => Some(encoding_rs::WINDOWS_1250),
87        1251 => Some(encoding_rs::WINDOWS_1251),
88        1252 => Some(encoding_rs::WINDOWS_1252),
89        1253 => Some(encoding_rs::WINDOWS_1253),
90        1254 => Some(encoding_rs::WINDOWS_1254),
91        1255 => Some(encoding_rs::WINDOWS_1255),
92        1256 => Some(encoding_rs::WINDOWS_1256),
93        1257 => Some(encoding_rs::WINDOWS_1257),
94        1258 => Some(encoding_rs::WINDOWS_1258),
95        _ => None,
96    }
97}
98
99/// Get the Windows code page for an LCID value.
100#[cfg(feature = "encoding")]
101fn code_page_for_lcid(lcid: u32) -> Option<u16> {
102    // Mask for primary language ID (lower 10 bits)
103    const PRIMARY_LANGUAGE_MASK: u32 = 0x3FF;
104    let primary_lang = lcid & PRIMARY_LANGUAGE_MASK;
105
106    match primary_lang {
107        0x0411 => Some(932),                   // Japanese - Shift_JIS
108        0x0804 | 0x1004 => Some(936),          // Chinese Simplified - GBK
109        0x0404 | 0x0C04 | 0x1404 => Some(950), // Chinese Traditional - Big5
110        0x0412 => Some(949),                   // Korean - EUC-KR
111        0x041E => Some(874),                   // Thai
112        0x042A => Some(1258),                  // Vietnamese
113
114        // Code Page 1250 - Central European
115        0x0405 | 0x0415 | 0x040E | 0x041A | 0x081A | 0x141A | 0x101A | 0x041B | 0x0424 | 0x0418
116        | 0x041C => Some(1250),
117
118        // Code Page 1251 - Cyrillic
119        0x0419 | 0x0422 | 0x0423 | 0x0402 | 0x042F | 0x0C1A | 0x201A | 0x0440 | 0x0843 | 0x0444
120        | 0x0450 | 0x0485 => Some(1251),
121
122        0x0408 => Some(1253),          // Greek
123        0x041F | 0x042C => Some(1254), // Turkish, Azerbaijani
124        0x040D => Some(1255),          // Hebrew
125
126        // Code Page 1256 - Arabic
127        0x0401 | 0x0801 | 0x0C01 | 0x1001 | 0x1401 | 0x1801 | 0x1C01 | 0x2001 | 0x2401 | 0x2801
128        | 0x2C01 | 0x3001 | 0x3401 | 0x3801 | 0x3C01 | 0x4001 | 0x0429 | 0x0420 | 0x048C
129        | 0x0463 => Some(1256),
130
131        // Code Page 1257 - Baltic
132        0x0425..=0x0427 => Some(1257),
133
134        // Default to 1252 (Western European) for English and related languages
135        0x0409 | 0x0809 | 0x0C09 | 0x1009 | 0x1409 | 0x1809 | 0x1C09 | 0x2009 | 0x2409 | 0x2809
136        | 0x2C09 | 0x3009 | 0x3409 | 0x0407 | 0x0807 | 0x0C07 | 0x1007 | 0x1407 | 0x040C
137        | 0x080C | 0x0C0C | 0x100C | 0x140C | 0x180C | 0x0410 | 0x0810 | 0x0413 | 0x0813
138        | 0x0416 | 0x0816 | 0x040A | 0x080A | 0x0C0A | 0x100A | 0x140A | 0x180A | 0x1C0A
139        | 0x200A | 0x240A | 0x280A | 0x2C0A | 0x300A | 0x340A | 0x380A | 0x3C0A | 0x400A
140        | 0x440A | 0x480A | 0x4C0A | 0x500A => Some(1252),
141
142        _ => Some(1252), // Default fallback
143    }
144}
145
146impl TypeInfo {
147    /// Create type info for a fixed-length integer type.
148    #[must_use]
149    pub fn int(type_id: u8) -> Self {
150        Self {
151            type_id,
152            length: None,
153            scale: None,
154            precision: None,
155            collation: None,
156        }
157    }
158
159    /// Create type info for a variable-length type.
160    #[must_use]
161    pub fn varchar(length: u32) -> Self {
162        Self {
163            type_id: 0xE7, // NVARCHARTYPE
164            length: Some(length),
165            scale: None,
166            precision: None,
167            collation: None,
168        }
169    }
170
171    /// Create type info for a decimal type.
172    #[must_use]
173    pub fn decimal(precision: u8, scale: u8) -> Self {
174        Self {
175            type_id: 0x6C,
176            length: None,
177            scale: Some(scale),
178            precision: Some(precision),
179            collation: None,
180        }
181    }
182
183    /// Create type info for a datetime type with scale.
184    #[must_use]
185    pub fn datetime_with_scale(type_id: u8, scale: u8) -> Self {
186        Self {
187            type_id,
188            length: None,
189            scale: Some(scale),
190            precision: None,
191            collation: None,
192        }
193    }
194}
195
196/// Decode a SQL value based on type information.
197pub fn decode_value(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
198    match type_info.type_id {
199        // Fixed-length types
200        0x1F => Ok(SqlValue::Null),   // NULLTYPE
201        0x32 => decode_bit(buf),      // BITTYPE
202        0x30 => decode_tinyint(buf),  // INT1TYPE
203        0x34 => decode_smallint(buf), // INT2TYPE
204        0x38 => decode_int(buf),      // INT4TYPE
205        0x7F => decode_bigint(buf),   // INT8TYPE
206        0x3B => decode_float(buf),    // FLT4TYPE
207        0x3E => decode_double(buf),   // FLT8TYPE
208
209        // Nullable integer types (INTNTYPE)
210        0x26 => decode_intn(buf, type_info),
211
212        // Variable-length string types
213        0xE7 => decode_nvarchar(buf, type_info), // NVARCHARTYPE
214        0xAF => decode_varchar(buf, type_info),  // BIGCHARTYPE
215        0xA7 => decode_varchar(buf, type_info),  // BIGVARCHARTYPE
216
217        // Binary types
218        0xA5 => decode_varbinary(buf, type_info), // BIGVARBINTYPE
219        0xAD => decode_varbinary(buf, type_info), // BIGBINARYTYPE
220
221        // GUID
222        0x24 => decode_guid(buf),
223
224        // Decimal/Numeric
225        0x6C | 0x6A => decode_decimal(buf, type_info),
226
227        // Date/Time types
228        0x28 => decode_date(buf),                      // DATETYPE
229        0x29 => decode_time(buf, type_info),           // TIMETYPE
230        0x2A => decode_datetime2(buf, type_info),      // DATETIME2TYPE
231        0x2B => decode_datetimeoffset(buf, type_info), // DATETIMEOFFSETTYPE
232        0x3D => decode_datetime(buf),                  // DATETIMETYPE
233        0x3F => decode_smalldatetime(buf),             // SMALLDATETIMETYPE
234
235        // XML
236        0xF1 => decode_xml(buf),
237
238        _ => Err(TypeError::UnsupportedConversion {
239            from: format!("TDS type 0x{:02X}", type_info.type_id),
240            to: "SqlValue",
241        }),
242    }
243}
244
245fn decode_bit(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
246    if buf.remaining() < 1 {
247        return Err(TypeError::BufferTooSmall {
248            needed: 1,
249            available: buf.remaining(),
250        });
251    }
252    Ok(SqlValue::Bool(buf.get_u8() != 0))
253}
254
255fn decode_tinyint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
256    if buf.remaining() < 1 {
257        return Err(TypeError::BufferTooSmall {
258            needed: 1,
259            available: buf.remaining(),
260        });
261    }
262    Ok(SqlValue::TinyInt(buf.get_u8()))
263}
264
265fn decode_smallint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
266    if buf.remaining() < 2 {
267        return Err(TypeError::BufferTooSmall {
268            needed: 2,
269            available: buf.remaining(),
270        });
271    }
272    Ok(SqlValue::SmallInt(buf.get_i16_le()))
273}
274
275fn decode_int(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
276    if buf.remaining() < 4 {
277        return Err(TypeError::BufferTooSmall {
278            needed: 4,
279            available: buf.remaining(),
280        });
281    }
282    Ok(SqlValue::Int(buf.get_i32_le()))
283}
284
285fn decode_bigint(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
286    if buf.remaining() < 8 {
287        return Err(TypeError::BufferTooSmall {
288            needed: 8,
289            available: buf.remaining(),
290        });
291    }
292    Ok(SqlValue::BigInt(buf.get_i64_le()))
293}
294
295fn decode_float(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
296    if buf.remaining() < 4 {
297        return Err(TypeError::BufferTooSmall {
298            needed: 4,
299            available: buf.remaining(),
300        });
301    }
302    Ok(SqlValue::Float(buf.get_f32_le()))
303}
304
305fn decode_double(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
306    if buf.remaining() < 8 {
307        return Err(TypeError::BufferTooSmall {
308            needed: 8,
309            available: buf.remaining(),
310        });
311    }
312    Ok(SqlValue::Double(buf.get_f64_le()))
313}
314
315fn decode_intn(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
316    if buf.remaining() < 1 {
317        return Err(TypeError::BufferTooSmall {
318            needed: 1,
319            available: buf.remaining(),
320        });
321    }
322
323    let actual_len = buf.get_u8() as usize;
324    if actual_len == 0 {
325        return Ok(SqlValue::Null);
326    }
327
328    if buf.remaining() < actual_len {
329        return Err(TypeError::BufferTooSmall {
330            needed: actual_len,
331            available: buf.remaining(),
332        });
333    }
334
335    match actual_len {
336        1 => Ok(SqlValue::TinyInt(buf.get_u8())),
337        2 => Ok(SqlValue::SmallInt(buf.get_i16_le())),
338        4 => Ok(SqlValue::Int(buf.get_i32_le())),
339        8 => Ok(SqlValue::BigInt(buf.get_i64_le())),
340        _ => Err(TypeError::InvalidBinary(format!(
341            "invalid INTN length: {actual_len}"
342        ))),
343    }
344}
345
346fn decode_nvarchar(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
347    if buf.remaining() < 2 {
348        return Err(TypeError::BufferTooSmall {
349            needed: 2,
350            available: buf.remaining(),
351        });
352    }
353
354    let byte_len = buf.get_u16_le() as usize;
355
356    // 0xFFFF indicates NULL
357    if byte_len == 0xFFFF {
358        return Ok(SqlValue::Null);
359    }
360
361    if buf.remaining() < byte_len {
362        return Err(TypeError::BufferTooSmall {
363            needed: byte_len,
364            available: buf.remaining(),
365        });
366    }
367
368    let utf16_data = buf.copy_to_bytes(byte_len);
369    let s = decode_utf16_string(&utf16_data)?;
370    Ok(SqlValue::String(s))
371}
372
373fn decode_varchar(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
374    if buf.remaining() < 2 {
375        return Err(TypeError::BufferTooSmall {
376            needed: 2,
377            available: buf.remaining(),
378        });
379    }
380
381    let byte_len = buf.get_u16_le() as usize;
382
383    // 0xFFFF indicates NULL
384    if byte_len == 0xFFFF {
385        return Ok(SqlValue::Null);
386    }
387
388    if buf.remaining() < byte_len {
389        return Err(TypeError::BufferTooSmall {
390            needed: byte_len,
391            available: buf.remaining(),
392        });
393    }
394
395    let data = buf.copy_to_bytes(byte_len);
396
397    // Try UTF-8 first (most common case and zero-cost for ASCII)
398    if let Ok(s) = String::from_utf8(data.to_vec()) {
399        return Ok(SqlValue::String(s));
400    }
401
402    // If UTF-8 fails, try collation-aware decoding
403    #[cfg(feature = "encoding")]
404    if let Some(ref collation) = type_info.collation {
405        if let Some(encoding) = collation.encoding() {
406            let (decoded, _, had_errors) = encoding.decode(&data);
407            if !had_errors {
408                return Ok(SqlValue::String(decoded.into_owned()));
409            }
410        }
411    }
412
413    // Suppress unused warning when encoding feature is disabled
414    #[cfg(not(feature = "encoding"))]
415    let _ = type_info;
416
417    // Fallback: lossy UTF-8 conversion
418    Ok(SqlValue::String(
419        String::from_utf8_lossy(&data).into_owned(),
420    ))
421}
422
423fn decode_varbinary(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
424    if buf.remaining() < 2 {
425        return Err(TypeError::BufferTooSmall {
426            needed: 2,
427            available: buf.remaining(),
428        });
429    }
430
431    let byte_len = buf.get_u16_le() as usize;
432
433    // 0xFFFF indicates NULL
434    if byte_len == 0xFFFF {
435        return Ok(SqlValue::Null);
436    }
437
438    if buf.remaining() < byte_len {
439        return Err(TypeError::BufferTooSmall {
440            needed: byte_len,
441            available: buf.remaining(),
442        });
443    }
444
445    let data = buf.copy_to_bytes(byte_len);
446    Ok(SqlValue::Binary(data))
447}
448
449#[cfg(feature = "uuid")]
450fn decode_guid(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
451    if buf.remaining() < 1 {
452        return Err(TypeError::BufferTooSmall {
453            needed: 1,
454            available: buf.remaining(),
455        });
456    }
457
458    let len = buf.get_u8() as usize;
459    if len == 0 {
460        return Ok(SqlValue::Null);
461    }
462
463    if len != 16 {
464        return Err(TypeError::InvalidBinary(format!(
465            "invalid GUID length: {len}"
466        )));
467    }
468
469    if buf.remaining() < 16 {
470        return Err(TypeError::BufferTooSmall {
471            needed: 16,
472            available: buf.remaining(),
473        });
474    }
475
476    // SQL Server stores UUIDs in mixed-endian format
477    let mut bytes = [0u8; 16];
478
479    // First 4 bytes - little-endian (reverse)
480    bytes[3] = buf.get_u8();
481    bytes[2] = buf.get_u8();
482    bytes[1] = buf.get_u8();
483    bytes[0] = buf.get_u8();
484
485    // Next 2 bytes - little-endian (reverse)
486    bytes[5] = buf.get_u8();
487    bytes[4] = buf.get_u8();
488
489    // Next 2 bytes - little-endian (reverse)
490    bytes[7] = buf.get_u8();
491    bytes[6] = buf.get_u8();
492
493    // Last 8 bytes - big-endian (keep as-is)
494    for byte in &mut bytes[8..16] {
495        *byte = buf.get_u8();
496    }
497
498    Ok(SqlValue::Uuid(uuid::Uuid::from_bytes(bytes)))
499}
500
501#[cfg(not(feature = "uuid"))]
502fn decode_guid(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
503    // Skip the GUID data
504    if buf.remaining() < 1 {
505        return Err(TypeError::BufferTooSmall {
506            needed: 1,
507            available: buf.remaining(),
508        });
509    }
510
511    let len = buf.get_u8() as usize;
512    if len == 0 {
513        return Ok(SqlValue::Null);
514    }
515
516    if buf.remaining() < len {
517        return Err(TypeError::BufferTooSmall {
518            needed: len,
519            available: buf.remaining(),
520        });
521    }
522
523    let data = buf.copy_to_bytes(len);
524    Ok(SqlValue::Binary(data))
525}
526
527#[cfg(feature = "decimal")]
528fn decode_decimal(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
529    use rust_decimal::Decimal;
530
531    if buf.remaining() < 1 {
532        return Err(TypeError::BufferTooSmall {
533            needed: 1,
534            available: buf.remaining(),
535        });
536    }
537
538    let len = buf.get_u8() as usize;
539    if len == 0 {
540        return Ok(SqlValue::Null);
541    }
542
543    if buf.remaining() < len {
544        return Err(TypeError::BufferTooSmall {
545            needed: len,
546            available: buf.remaining(),
547        });
548    }
549
550    // First byte is sign (0 = negative, 1 = positive)
551    let sign = buf.get_u8();
552    let remaining = len - 1;
553
554    // Read mantissa (little-endian)
555    let mut mantissa_bytes = [0u8; 16];
556    for byte in mantissa_bytes.iter_mut().take(remaining.min(16)) {
557        *byte = buf.get_u8();
558    }
559
560    let mantissa = u128::from_le_bytes(mantissa_bytes);
561    let scale = type_info.scale.unwrap_or(0) as u32;
562
563    let mut decimal = Decimal::from_i128_with_scale(mantissa as i128, scale);
564    if sign == 0 {
565        decimal.set_sign_negative(true);
566    }
567
568    Ok(SqlValue::Decimal(decimal))
569}
570
571#[cfg(not(feature = "decimal"))]
572fn decode_decimal(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
573    // Skip decimal data and return as string
574    if buf.remaining() < 1 {
575        return Err(TypeError::BufferTooSmall {
576            needed: 1,
577            available: buf.remaining(),
578        });
579    }
580
581    let len = buf.get_u8() as usize;
582    if len == 0 {
583        return Ok(SqlValue::Null);
584    }
585
586    if buf.remaining() < len {
587        return Err(TypeError::BufferTooSmall {
588            needed: len,
589            available: buf.remaining(),
590        });
591    }
592
593    buf.advance(len);
594    Ok(SqlValue::String("DECIMAL (feature disabled)".to_string()))
595}
596
597#[cfg(feature = "chrono")]
598fn decode_date(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
599    if buf.remaining() < 1 {
600        return Err(TypeError::BufferTooSmall {
601            needed: 1,
602            available: buf.remaining(),
603        });
604    }
605
606    let len = buf.get_u8() as usize;
607    if len == 0 {
608        return Ok(SqlValue::Null);
609    }
610
611    if len != 3 {
612        return Err(TypeError::InvalidDateTime(format!(
613            "invalid DATE length: {len}"
614        )));
615    }
616
617    if buf.remaining() < 3 {
618        return Err(TypeError::BufferTooSmall {
619            needed: 3,
620            available: buf.remaining(),
621        });
622    }
623
624    // 3 bytes little-endian representing days since 0001-01-01
625    let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
626
627    let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
628    let date = base + chrono::Duration::days(days as i64);
629
630    Ok(SqlValue::Date(date))
631}
632
633#[cfg(not(feature = "chrono"))]
634fn decode_date(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
635    if buf.remaining() < 1 {
636        return Err(TypeError::BufferTooSmall {
637            needed: 1,
638            available: buf.remaining(),
639        });
640    }
641
642    let len = buf.get_u8() as usize;
643    if len == 0 {
644        return Ok(SqlValue::Null);
645    }
646
647    if buf.remaining() < len {
648        return Err(TypeError::BufferTooSmall {
649            needed: len,
650            available: buf.remaining(),
651        });
652    }
653
654    buf.advance(len);
655    Ok(SqlValue::String("DATE (feature disabled)".to_string()))
656}
657
658#[cfg(feature = "chrono")]
659fn decode_time(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
660    let scale = type_info.scale.unwrap_or(7);
661    let time_len = time_bytes_for_scale(scale);
662
663    if buf.remaining() < 1 {
664        return Err(TypeError::BufferTooSmall {
665            needed: 1,
666            available: buf.remaining(),
667        });
668    }
669
670    let len = buf.get_u8() as usize;
671    if len == 0 {
672        return Ok(SqlValue::Null);
673    }
674
675    if buf.remaining() < len {
676        return Err(TypeError::BufferTooSmall {
677            needed: len,
678            available: buf.remaining(),
679        });
680    }
681
682    // Read time bytes (variable length based on scale)
683    let mut time_bytes = [0u8; 8];
684    for byte in time_bytes.iter_mut().take(time_len) {
685        *byte = buf.get_u8();
686    }
687
688    let intervals = u64::from_le_bytes(time_bytes);
689    let time = intervals_to_time(intervals, scale);
690
691    Ok(SqlValue::Time(time))
692}
693
694#[cfg(not(feature = "chrono"))]
695fn decode_time(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
696    if buf.remaining() < 1 {
697        return Err(TypeError::BufferTooSmall {
698            needed: 1,
699            available: buf.remaining(),
700        });
701    }
702
703    let len = buf.get_u8() as usize;
704    if len == 0 {
705        return Ok(SqlValue::Null);
706    }
707
708    if buf.remaining() < len {
709        return Err(TypeError::BufferTooSmall {
710            needed: len,
711            available: buf.remaining(),
712        });
713    }
714
715    buf.advance(len);
716    Ok(SqlValue::String("TIME (feature disabled)".to_string()))
717}
718
719#[cfg(feature = "chrono")]
720fn decode_datetime2(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
721    let scale = type_info.scale.unwrap_or(7);
722    let time_len = time_bytes_for_scale(scale);
723
724    if buf.remaining() < 1 {
725        return Err(TypeError::BufferTooSmall {
726            needed: 1,
727            available: buf.remaining(),
728        });
729    }
730
731    let len = buf.get_u8() as usize;
732    if len == 0 {
733        return Ok(SqlValue::Null);
734    }
735
736    if buf.remaining() < len {
737        return Err(TypeError::BufferTooSmall {
738            needed: len,
739            available: buf.remaining(),
740        });
741    }
742
743    // Decode time
744    let mut time_bytes = [0u8; 8];
745    for byte in time_bytes.iter_mut().take(time_len) {
746        *byte = buf.get_u8();
747    }
748    let intervals = u64::from_le_bytes(time_bytes);
749    let time = intervals_to_time(intervals, scale);
750
751    // Decode date
752    let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
753    let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
754    let date = base + chrono::Duration::days(days as i64);
755
756    Ok(SqlValue::DateTime(date.and_time(time)))
757}
758
759#[cfg(not(feature = "chrono"))]
760fn decode_datetime2(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
761    if buf.remaining() < 1 {
762        return Err(TypeError::BufferTooSmall {
763            needed: 1,
764            available: buf.remaining(),
765        });
766    }
767
768    let len = buf.get_u8() as usize;
769    if len == 0 {
770        return Ok(SqlValue::Null);
771    }
772
773    if buf.remaining() < len {
774        return Err(TypeError::BufferTooSmall {
775            needed: len,
776            available: buf.remaining(),
777        });
778    }
779
780    buf.advance(len);
781    Ok(SqlValue::String("DATETIME2 (feature disabled)".to_string()))
782}
783
784#[cfg(feature = "chrono")]
785fn decode_datetimeoffset(buf: &mut Bytes, type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
786    use chrono::TimeZone;
787
788    let scale = type_info.scale.unwrap_or(7);
789    let time_len = time_bytes_for_scale(scale);
790
791    if buf.remaining() < 1 {
792        return Err(TypeError::BufferTooSmall {
793            needed: 1,
794            available: buf.remaining(),
795        });
796    }
797
798    let len = buf.get_u8() as usize;
799    if len == 0 {
800        return Ok(SqlValue::Null);
801    }
802
803    if buf.remaining() < len {
804        return Err(TypeError::BufferTooSmall {
805            needed: len,
806            available: buf.remaining(),
807        });
808    }
809
810    // Decode time
811    let mut time_bytes = [0u8; 8];
812    for byte in time_bytes.iter_mut().take(time_len) {
813        *byte = buf.get_u8();
814    }
815    let intervals = u64::from_le_bytes(time_bytes);
816    let time = intervals_to_time(intervals, scale);
817
818    // Decode date
819    let days = buf.get_u8() as u32 | ((buf.get_u8() as u32) << 8) | ((buf.get_u8() as u32) << 16);
820    let base = chrono::NaiveDate::from_ymd_opt(1, 1, 1).expect("valid date");
821    let date = base + chrono::Duration::days(days as i64);
822
823    // Decode timezone offset in minutes
824    let offset_minutes = buf.get_i16_le();
825    let offset = chrono::FixedOffset::east_opt((offset_minutes as i32) * 60)
826        .ok_or_else(|| TypeError::InvalidDateTime(format!("invalid offset: {offset_minutes}")))?;
827
828    let datetime = offset
829        .from_local_datetime(&date.and_time(time))
830        .single()
831        .ok_or_else(|| TypeError::InvalidDateTime("ambiguous datetime".to_string()))?;
832
833    Ok(SqlValue::DateTimeOffset(datetime))
834}
835
836#[cfg(not(feature = "chrono"))]
837fn decode_datetimeoffset(buf: &mut Bytes, _type_info: &TypeInfo) -> Result<SqlValue, TypeError> {
838    if buf.remaining() < 1 {
839        return Err(TypeError::BufferTooSmall {
840            needed: 1,
841            available: buf.remaining(),
842        });
843    }
844
845    let len = buf.get_u8() as usize;
846    if len == 0 {
847        return Ok(SqlValue::Null);
848    }
849
850    if buf.remaining() < len {
851        return Err(TypeError::BufferTooSmall {
852            needed: len,
853            available: buf.remaining(),
854        });
855    }
856
857    buf.advance(len);
858    Ok(SqlValue::String(
859        "DATETIMEOFFSET (feature disabled)".to_string(),
860    ))
861}
862
863#[cfg(feature = "chrono")]
864fn decode_datetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
865    // DATETIME is 8 bytes: 4 bytes days since 1900-01-01 + 4 bytes 300ths of second
866    if buf.remaining() < 8 {
867        return Err(TypeError::BufferTooSmall {
868            needed: 8,
869            available: buf.remaining(),
870        });
871    }
872
873    let days = buf.get_i32_le();
874    let time_300ths = buf.get_u32_le();
875
876    let base = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("valid date");
877    let date = base + chrono::Duration::days(days as i64);
878
879    // Convert 300ths of second to time
880    let total_ms = (time_300ths as u64 * 1000) / 300;
881    let secs = (total_ms / 1000) as u32;
882    let nanos = ((total_ms % 1000) * 1_000_000) as u32;
883
884    let time = chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, nanos)
885        .ok_or_else(|| TypeError::InvalidDateTime("invalid DATETIME time".to_string()))?;
886
887    Ok(SqlValue::DateTime(date.and_time(time)))
888}
889
890#[cfg(not(feature = "chrono"))]
891fn decode_datetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
892    if buf.remaining() < 8 {
893        return Err(TypeError::BufferTooSmall {
894            needed: 8,
895            available: buf.remaining(),
896        });
897    }
898
899    buf.advance(8);
900    Ok(SqlValue::String("DATETIME (feature disabled)".to_string()))
901}
902
903#[cfg(feature = "chrono")]
904fn decode_smalldatetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
905    // SMALLDATETIME is 4 bytes: 2 bytes days since 1900-01-01 + 2 bytes minutes
906    if buf.remaining() < 4 {
907        return Err(TypeError::BufferTooSmall {
908            needed: 4,
909            available: buf.remaining(),
910        });
911    }
912
913    let days = buf.get_u16_le();
914    let minutes = buf.get_u16_le();
915
916    let base = chrono::NaiveDate::from_ymd_opt(1900, 1, 1).expect("valid date");
917    let date = base + chrono::Duration::days(days as i64);
918
919    let time = chrono::NaiveTime::from_num_seconds_from_midnight_opt((minutes as u32) * 60, 0)
920        .ok_or_else(|| TypeError::InvalidDateTime("invalid SMALLDATETIME time".to_string()))?;
921
922    Ok(SqlValue::DateTime(date.and_time(time)))
923}
924
925#[cfg(not(feature = "chrono"))]
926fn decode_smalldatetime(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
927    if buf.remaining() < 4 {
928        return Err(TypeError::BufferTooSmall {
929            needed: 4,
930            available: buf.remaining(),
931        });
932    }
933
934    buf.advance(4);
935    Ok(SqlValue::String(
936        "SMALLDATETIME (feature disabled)".to_string(),
937    ))
938}
939
940fn decode_xml(buf: &mut Bytes) -> Result<SqlValue, TypeError> {
941    // XML is sent as UTF-16LE string with length prefix
942    if buf.remaining() < 2 {
943        return Err(TypeError::BufferTooSmall {
944            needed: 2,
945            available: buf.remaining(),
946        });
947    }
948
949    let byte_len = buf.get_u16_le() as usize;
950
951    if byte_len == 0xFFFF {
952        return Ok(SqlValue::Null);
953    }
954
955    if buf.remaining() < byte_len {
956        return Err(TypeError::BufferTooSmall {
957            needed: byte_len,
958            available: buf.remaining(),
959        });
960    }
961
962    let utf16_data = buf.copy_to_bytes(byte_len);
963    let s = decode_utf16_string(&utf16_data)?;
964    Ok(SqlValue::Xml(s))
965}
966
967/// Decode a UTF-16LE string from bytes.
968pub fn decode_utf16_string(data: &[u8]) -> Result<String, TypeError> {
969    if data.len() % 2 != 0 {
970        return Err(TypeError::InvalidEncoding(
971            "UTF-16 data must have even length".to_string(),
972        ));
973    }
974
975    let utf16: Vec<u16> = data
976        .chunks_exact(2)
977        .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
978        .collect();
979
980    String::from_utf16(&utf16).map_err(|e| TypeError::InvalidEncoding(e.to_string()))
981}
982
983/// Calculate number of bytes needed for TIME based on scale.
984#[cfg(feature = "chrono")]
985fn time_bytes_for_scale(scale: u8) -> usize {
986    match scale {
987        0..=2 => 3,
988        3..=4 => 4,
989        5..=7 => 5,
990        _ => 5, // Default to max precision
991    }
992}
993
994/// Convert 100-nanosecond intervals to NaiveTime.
995#[cfg(feature = "chrono")]
996fn intervals_to_time(intervals: u64, scale: u8) -> chrono::NaiveTime {
997    // Scale determines the unit:
998    // scale 0: seconds
999    // scale 1: 100ms
1000    // scale 2: 10ms
1001    // scale 3: 1ms
1002    // scale 4: 100us
1003    // scale 5: 10us
1004    // scale 6: 1us
1005    // scale 7: 100ns
1006
1007    let nanos = match scale {
1008        0 => intervals * 1_000_000_000,
1009        1 => intervals * 100_000_000,
1010        2 => intervals * 10_000_000,
1011        3 => intervals * 1_000_000,
1012        4 => intervals * 100_000,
1013        5 => intervals * 10_000,
1014        6 => intervals * 1_000,
1015        7 => intervals * 100,
1016        _ => intervals * 100,
1017    };
1018
1019    let secs = (nanos / 1_000_000_000) as u32;
1020    let nano_part = (nanos % 1_000_000_000) as u32;
1021
1022    chrono::NaiveTime::from_num_seconds_from_midnight_opt(secs, nano_part)
1023        .unwrap_or_else(|| chrono::NaiveTime::from_hms_opt(0, 0, 0).expect("valid time"))
1024}
1025
1026#[cfg(test)]
1027#[allow(clippy::unwrap_used)]
1028mod tests {
1029    use super::*;
1030
1031    #[test]
1032    fn test_decode_int() {
1033        let mut buf = Bytes::from_static(&[42, 0, 0, 0]);
1034        let type_info = TypeInfo::int(0x38);
1035        let result = decode_value(&mut buf, &type_info).unwrap();
1036        assert_eq!(result, SqlValue::Int(42));
1037    }
1038
1039    #[test]
1040    fn test_decode_utf16_string() {
1041        // "AB" in UTF-16LE
1042        let data = [0x41, 0x00, 0x42, 0x00];
1043        let result = decode_utf16_string(&data).unwrap();
1044        assert_eq!(result, "AB");
1045    }
1046
1047    #[test]
1048    fn test_decode_nvarchar() {
1049        // Length (4 bytes for "AB") + "AB" in UTF-16LE
1050        let mut buf = Bytes::from_static(&[4, 0, 0x41, 0x00, 0x42, 0x00]);
1051        let type_info = TypeInfo::varchar(100);
1052        let type_info = TypeInfo {
1053            type_id: 0xE7,
1054            ..type_info
1055        };
1056        let result = decode_value(&mut buf, &type_info).unwrap();
1057        assert_eq!(result, SqlValue::String("AB".to_string()));
1058    }
1059
1060    #[test]
1061    fn test_decode_null_nvarchar() {
1062        // 0xFFFF indicates NULL
1063        let mut buf = Bytes::from_static(&[0xFF, 0xFF]);
1064        let type_info = TypeInfo {
1065            type_id: 0xE7,
1066            length: Some(100),
1067            scale: None,
1068            precision: None,
1069            collation: None,
1070        };
1071        let result = decode_value(&mut buf, &type_info).unwrap();
1072        assert_eq!(result, SqlValue::Null);
1073    }
1074}