Skip to main content

rustlavel_db/sqlserver/
types.rs

1//! TDS types: reading COLMETADATA and rows into [`Value`], and encoding bound
2//! parameters on the way out.
3//!
4//! SQL Server sends everything in binary, so unlike the PostgreSQL driver —
5//! which asks for text and parses it — this module owns a decoder per type.
6//! Two decisions are worth stating up front, because they look like omissions:
7//!
8//! * `decimal`, `numeric` and `money` come back as [`Value::Text`]. A `decimal`
9//!   is an exact type; turning it into `f64` would quietly lose the precision
10//!   the column exists to preserve, and the framework has no decimal type yet.
11//! * `date`, `time`, `datetime`, `datetime2` and `datetimeoffset` come back as
12//!   text too, in ISO 8601 order. The framework has no date type yet, and the
13//!   PostgreSQL driver already keeps timestamps as text, so a model that reads
14//!   a timestamp as `String` behaves the same on both databases.
15//!
16//! `sql_variant` is the one type this module cannot promise: it is decoded when
17//! its base type is one of the common scalars and returned as NULL otherwise,
18//! having consumed exactly the right number of bytes so the token stream stays
19//! aligned.
20
21use super::protocol::{Reader, decode_ucs2};
22use crate::dialect::{Dialect, SqlServer};
23use crate::value::Value;
24use rustlavel_core::{Error, Json, Result};
25
26// Fixed-length types: the length is implied by the type byte.
27pub const NULLTYPE: u8 = 0x1F;
28pub const INT1TYPE: u8 = 0x30;
29pub const BITTYPE: u8 = 0x32;
30pub const INT2TYPE: u8 = 0x34;
31pub const INT4TYPE: u8 = 0x38;
32pub const DATETIM4TYPE: u8 = 0x3A;
33pub const FLT4TYPE: u8 = 0x3B;
34pub const MONEYTYPE: u8 = 0x3C;
35pub const DATETIMETYPE: u8 = 0x3D;
36pub const FLT8TYPE: u8 = 0x3E;
37pub const MONEY4TYPE: u8 = 0x7A;
38pub const INT8TYPE: u8 = 0x7F;
39
40// Nullable and variable types with a one-byte length.
41pub const GUIDTYPE: u8 = 0x24;
42pub const INTNTYPE: u8 = 0x26;
43pub const DECIMALTYPE: u8 = 0x37;
44pub const NUMERICTYPE: u8 = 0x3F;
45pub const BITNTYPE: u8 = 0x68;
46pub const DECIMALNTYPE: u8 = 0x6A;
47pub const NUMERICNTYPE: u8 = 0x6C;
48pub const FLTNTYPE: u8 = 0x6D;
49pub const MONEYNTYPE: u8 = 0x6E;
50pub const DATETIMNTYPE: u8 = 0x6F;
51pub const DATENTYPE: u8 = 0x28;
52pub const TIMENTYPE: u8 = 0x29;
53pub const DATETIME2NTYPE: u8 = 0x2A;
54pub const DATETIMEOFFSETNTYPE: u8 = 0x2B;
55pub const CHARTYPE: u8 = 0x2F;
56pub const VARCHARTYPE: u8 = 0x27;
57pub const BINARYTYPE: u8 = 0x2D;
58pub const VARBINARYTYPE: u8 = 0x25;
59
60// Types with a two-byte length. `BIG` is historical: they are the ones that can
61// exceed 255 bytes, and the `(max)` forms are these with a length of 0xFFFF.
62pub const BIGVARBINARYTYPE: u8 = 0xA5;
63pub const BIGVARCHARTYPE: u8 = 0xA7;
64pub const BIGBINARYTYPE: u8 = 0xAD;
65pub const BIGCHARTYPE: u8 = 0xAF;
66pub const NVARCHARTYPE: u8 = 0xE7;
67pub const NCHARTYPE: u8 = 0xEF;
68
69// Types with a four-byte length: the deprecated large-object types.
70pub const IMAGETYPE: u8 = 0x22;
71pub const TEXTTYPE: u8 = 0x23;
72pub const NTEXTTYPE: u8 = 0x63;
73pub const SSVARIANTTYPE: u8 = 0x62;
74pub const XMLTYPE: u8 = 0xF1;
75
76/// The marker a two-byte length carries when a column is declared `(max)`.
77const MAX_LENGTH: usize = 0xFFFF;
78
79/// How the value that follows a TYPE_INFO announces its own length.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum LengthStyle {
82    /// The type byte implies the size and the value can never be NULL.
83    Fixed,
84    /// One length byte; zero means NULL.
85    Byte,
86    /// Two length bytes; 0xFFFF means NULL.
87    Short,
88    /// A text pointer, then four length bytes. `text`, `ntext` and `image`.
89    Long,
90    /// Partially length-prefixed: a total length then a run of chunks. This is
91    /// how every `(max)` column arrives, and it is the only shape whose length
92    /// may genuinely be unknown when the first byte is sent.
93    Chunked,
94}
95
96/// A column's declared type, as COLMETADATA describes it.
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub struct TypeInfo {
99    pub kind: u8,
100    /// The declared maximum size in bytes, or [`MAX_LENGTH`] for `(max)`.
101    pub size: usize,
102    pub precision: u8,
103    pub scale: u8,
104    pub collation: Option<Collation>,
105    pub length_style: LengthStyle,
106}
107
108impl TypeInfo {
109    fn fixed(kind: u8, size: usize) -> TypeInfo {
110        TypeInfo {
111            kind,
112            size,
113            precision: 0,
114            scale: 0,
115            collation: None,
116            length_style: LengthStyle::Fixed,
117        }
118    }
119
120    fn with_style(kind: u8, size: usize, length_style: LengthStyle) -> TypeInfo {
121        TypeInfo { kind, size, precision: 0, scale: 0, collation: None, length_style }
122    }
123}
124
125/// The five collation bytes that follow every character type.
126///
127/// Only one bit of it changes how the driver behaves — whether the column is
128/// stored as UTF-8, which SQL Server 2019 introduced — but the LCID is kept so
129/// a caller diagnosing a mojibake bug can see what the server claimed.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct Collation {
132    pub lcid: u32,
133    pub flags: u32,
134    pub sort_id: u8,
135}
136
137impl Collation {
138    fn parse(bytes: &[u8]) -> Collation {
139        let word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
140        Collation { lcid: word & 0x000F_FFFF, flags: word, sort_id: bytes[4] }
141    }
142
143    /// The fUTF8 bit, set by the `_UTF8` collations.
144    pub fn is_utf8(&self) -> bool {
145        self.flags & 0x0400_0000 != 0
146    }
147}
148
149/// One column of a result set.
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct Column {
152    pub name: String,
153    pub type_info: TypeInfo,
154}
155
156/// Read a COLMETADATA token body.
157pub fn parse_column_metadata(reader: &mut Reader<'_>) -> Result<Vec<Column>> {
158    let count = reader.u16()?;
159    // 0xFFFF means "no metadata", which a statement returning nothing sends.
160    if count == 0xFFFF {
161        return Ok(Vec::new());
162    }
163
164    let mut columns = Vec::with_capacity(count as usize);
165    for _ in 0..count {
166        reader.u32()?; // user type
167        reader.u16()?; // flags
168        let type_info = parse_type_info(reader)?;
169
170        // The large-object types carry the table they came from, which is only
171        // read here to step over it.
172        if matches!(type_info.kind, TEXTTYPE | NTEXTTYPE | IMAGETYPE) {
173            let parts = reader.u8()?;
174            for _ in 0..parts {
175                reader.us_varchar()?;
176            }
177        }
178
179        columns.push(Column { name: reader.b_varchar()?, type_info });
180    }
181
182    Ok(columns)
183}
184
185/// Read a TYPE_INFO: the type byte and whatever describes its size.
186pub fn parse_type_info(reader: &mut Reader<'_>) -> Result<TypeInfo> {
187    let kind = reader.u8()?;
188
189    Ok(match kind {
190        NULLTYPE => TypeInfo::fixed(kind, 0),
191        INT1TYPE | BITTYPE => TypeInfo::fixed(kind, 1),
192        INT2TYPE => TypeInfo::fixed(kind, 2),
193        INT4TYPE | FLT4TYPE | MONEY4TYPE | DATETIM4TYPE => TypeInfo::fixed(kind, 4),
194        INT8TYPE | FLT8TYPE | MONEYTYPE | DATETIMETYPE => TypeInfo::fixed(kind, 8),
195
196        GUIDTYPE | INTNTYPE | BITNTYPE | FLTNTYPE | MONEYNTYPE | DATETIMNTYPE | CHARTYPE
197        | VARCHARTYPE | BINARYTYPE | VARBINARYTYPE => {
198            let size = reader.u8()? as usize;
199            TypeInfo::with_style(kind, size, LengthStyle::Byte)
200        }
201
202        DECIMALTYPE | NUMERICTYPE | DECIMALNTYPE | NUMERICNTYPE => {
203            let size = reader.u8()? as usize;
204            TypeInfo {
205                kind,
206                size,
207                precision: reader.u8()?,
208                scale: reader.u8()?,
209                collation: None,
210                length_style: LengthStyle::Byte,
211            }
212        }
213
214        // `date` is the one temporal type with no scale: it is always three
215        // bytes of day count.
216        DATENTYPE => TypeInfo::with_style(kind, 3, LengthStyle::Byte),
217
218        TIMENTYPE | DATETIME2NTYPE | DATETIMEOFFSETNTYPE => TypeInfo {
219            kind,
220            size: 0,
221            precision: 0,
222            scale: reader.u8()?,
223            collation: None,
224            length_style: LengthStyle::Byte,
225        },
226
227        BIGVARBINARYTYPE | BIGBINARYTYPE => {
228            let size = reader.u16()? as usize;
229            TypeInfo::with_style(kind, size, length_for(size))
230        }
231
232        BIGVARCHARTYPE | BIGCHARTYPE | NVARCHARTYPE | NCHARTYPE => {
233            let size = reader.u16()? as usize;
234            let collation = Collation::parse(reader.take(5)?);
235            TypeInfo {
236                kind,
237                size,
238                precision: 0,
239                scale: 0,
240                collation: Some(collation),
241                length_style: length_for(size),
242            }
243        }
244
245        TEXTTYPE | NTEXTTYPE => {
246            let size = reader.u32()? as usize;
247            let collation = Collation::parse(reader.take(5)?);
248            TypeInfo {
249                kind,
250                size,
251                precision: 0,
252                scale: 0,
253                collation: Some(collation),
254                length_style: LengthStyle::Long,
255            }
256        }
257
258        IMAGETYPE => {
259            let size = reader.u32()? as usize;
260            TypeInfo::with_style(kind, size, LengthStyle::Long)
261        }
262
263        SSVARIANTTYPE => {
264            let size = reader.u32()? as usize;
265            TypeInfo::with_style(kind, size, LengthStyle::Long)
266        }
267
268        XMLTYPE => {
269            // A schema collection may be named; it is read only to skip it.
270            if reader.u8()? == 1 {
271                reader.b_varchar()?; // database
272                reader.b_varchar()?; // owning schema
273                reader.us_varchar()?; // collection name
274            }
275            TypeInfo::with_style(kind, MAX_LENGTH, LengthStyle::Chunked)
276        }
277
278        other => {
279            return Err(Error::Protocol(format!(
280                "TDS type 0x{other:02X} is not one this driver decodes"
281            )));
282        }
283    })
284}
285
286fn length_for(size: usize) -> LengthStyle {
287    if size == MAX_LENGTH { LengthStyle::Chunked } else { LengthStyle::Short }
288}
289
290/// Read one ROW token: every column, in order.
291pub fn read_row(reader: &mut Reader<'_>, columns: &[Column]) -> Result<Vec<Value>> {
292    columns.iter().map(|column| read_value(reader, &column.type_info)).collect()
293}
294
295/// Read one NBCROW token — a "null bitmap compressed" row.
296///
297/// A leading bitmap marks which columns are NULL, and those columns then send
298/// no bytes at all. It is pure bandwidth saving, and forgetting that the
299/// omitted columns have no length prefix is the classic way to desynchronise a
300/// TDS reader.
301pub fn read_nbc_row(reader: &mut Reader<'_>, columns: &[Column]) -> Result<Vec<Value>> {
302    let bitmap = reader.take(columns.len().div_ceil(8))?;
303
304    let mut values = Vec::with_capacity(columns.len());
305    for (index, column) in columns.iter().enumerate() {
306        // Bits run least-significant-first within each byte.
307        let is_null = bitmap[index / 8] & (1 << (index % 8)) != 0;
308        values.push(if is_null {
309            Value::Null
310        } else {
311            read_value(reader, &column.type_info)?
312        });
313    }
314
315    Ok(values)
316}
317
318/// Read one value, given the type its column was declared with.
319pub fn read_value(reader: &mut Reader<'_>, type_info: &TypeInfo) -> Result<Value> {
320    let bytes = match type_info.length_style {
321        LengthStyle::Fixed => {
322            if type_info.size == 0 {
323                return Ok(Value::Null);
324            }
325            reader.take(type_info.size)?.to_vec()
326        }
327        LengthStyle::Byte => match reader.u8()? {
328            0 => return Ok(Value::Null),
329            length => reader.take(length as usize)?.to_vec(),
330        },
331        LengthStyle::Short => match reader.u16()? {
332            0xFFFF => return Ok(Value::Null),
333            length => reader.take(length as usize)?.to_vec(),
334        },
335        LengthStyle::Long => match read_long(reader)? {
336            None => return Ok(Value::Null),
337            Some(bytes) => bytes,
338        },
339        LengthStyle::Chunked => match read_chunked(reader)? {
340            None => return Ok(Value::Null),
341            Some(bytes) => bytes,
342        },
343    };
344
345    decode(type_info, &bytes)
346}
347
348/// The `text`/`ntext`/`image` shape: a text pointer, a timestamp, then a length.
349fn read_long(reader: &mut Reader<'_>) -> Result<Option<Vec<u8>>> {
350    let pointer = reader.u8()? as usize;
351    if pointer == 0 {
352        return Ok(None);
353    }
354    reader.skip(pointer)?;
355    reader.skip(8)?; // the row's timestamp, which nothing here needs
356
357    let length = reader.u32()? as usize;
358    Ok(Some(reader.take(length)?.to_vec()))
359}
360
361/// The PLP shape every `(max)` column uses: a total length, then chunks, then a
362/// zero-length chunk to finish.
363///
364/// The total may be 0xFFFFFFFFFFFFFFFE — "unknown" — which is why the chunks
365/// are read until the terminator rather than until the total is reached.
366fn read_chunked(reader: &mut Reader<'_>) -> Result<Option<Vec<u8>>> {
367    const NULL: u64 = 0xFFFF_FFFF_FFFF_FFFF;
368    const UNKNOWN: u64 = 0xFFFF_FFFF_FFFF_FFFE;
369
370    let total = reader.u64()?;
371    if total == NULL {
372        return Ok(None);
373    }
374
375    let mut out = if total == UNKNOWN {
376        Vec::new()
377    } else {
378        Vec::with_capacity(total.min(1 << 20) as usize)
379    };
380
381    loop {
382        // A live SQL Server does send the terminator even for a zero-length
383        // value, so this only guards against one that does not — and it can
384        // only fire when the value is the last thing in the message, where
385        // reading a chunk length would fail anyway.
386        if total == 0 && reader.remaining() < 4 {
387            return Ok(Some(out));
388        }
389        let length = reader.u32()? as usize;
390        if length == 0 {
391            return Ok(Some(out));
392        }
393        out.extend_from_slice(reader.take(length)?);
394    }
395}
396
397/// Turn the raw bytes of one value into a [`Value`].
398fn decode(type_info: &TypeInfo, bytes: &[u8]) -> Result<Value> {
399    Ok(match type_info.kind {
400        BITTYPE | BITNTYPE => boolean(bytes.first().copied().unwrap_or(0)),
401
402        INT1TYPE => Value::Int(bytes[0] as i64),
403        INT2TYPE => Value::Int(i16::from_le_bytes(fixed(bytes)?) as i64),
404        INT4TYPE => Value::Int(i32::from_le_bytes(fixed(bytes)?) as i64),
405        INT8TYPE => Value::Int(i64::from_le_bytes(fixed(bytes)?)),
406        // `intn` is any of the four widths; the length that arrived says which.
407        INTNTYPE => match bytes.len() {
408            1 => Value::Int(bytes[0] as i64),
409            2 => Value::Int(i16::from_le_bytes(fixed(bytes)?) as i64),
410            4 => Value::Int(i32::from_le_bytes(fixed(bytes)?) as i64),
411            8 => Value::Int(i64::from_le_bytes(fixed(bytes)?)),
412            other => return Err(width("int", other)),
413        },
414
415        FLT4TYPE => Value::Float(f32::from_le_bytes(fixed(bytes)?) as f64),
416        FLT8TYPE => Value::Float(f64::from_le_bytes(fixed(bytes)?)),
417        FLTNTYPE => match bytes.len() {
418            4 => Value::Float(f32::from_le_bytes(fixed(bytes)?) as f64),
419            8 => Value::Float(f64::from_le_bytes(fixed(bytes)?)),
420            other => return Err(width("float", other)),
421        },
422
423        // `money` is a fixed four-decimal integer. Kept as text for the same
424        // reason as `decimal`: it is exact, and `f64` is not.
425        MONEYTYPE | MONEY4TYPE | MONEYNTYPE => Value::Text(money(bytes)?),
426
427        DECIMALTYPE | NUMERICTYPE | DECIMALNTYPE | NUMERICNTYPE => {
428            Value::Text(decimal(bytes, type_info.scale)?)
429        }
430
431        GUIDTYPE => Value::Text(uuid(bytes)?),
432
433        CHARTYPE | VARCHARTYPE | BIGCHARTYPE | BIGVARCHARTYPE | TEXTTYPE => {
434            Value::Text(decode_char(type_info.collation, bytes))
435        }
436
437        NCHARTYPE | NVARCHARTYPE | NTEXTTYPE | XMLTYPE => Value::Text(decode_ucs2(bytes)),
438
439        BINARYTYPE | VARBINARYTYPE | BIGBINARYTYPE | BIGVARBINARYTYPE | IMAGETYPE => {
440            Value::Bytes(bytes.to_vec())
441        }
442
443        DATENTYPE => Value::Text(date_text(days(bytes))),
444        TIMENTYPE => Value::Text(time_text(time_units(bytes), type_info.scale)),
445        DATETIME2NTYPE => Value::Text(datetime2_text(bytes, type_info.scale)?),
446        DATETIMEOFFSETNTYPE => Value::Text(datetimeoffset_text(bytes, type_info.scale)?),
447        DATETIMETYPE | DATETIM4TYPE | DATETIMNTYPE => Value::Text(legacy_datetime_text(bytes)?),
448
449        SSVARIANTTYPE => variant(bytes)?,
450
451        NULLTYPE => Value::Null,
452
453        other => {
454            return Err(Error::Protocol(format!(
455                "TDS type 0x{other:02X} arrived with no decoder"
456            )));
457        }
458    })
459}
460
461/// SQL Server has no boolean: `bit` is what the dialect maps `Boolean` onto,
462/// and [`Dialect::booleans_are_integers`] is where that fact is recorded. The
463/// wire hands over a byte; this is the one place that turns it back into the
464/// `Value::Bool` the rest of the framework expects, so a model field declared
465/// `bool` reads identically on PostgreSQL and on SQL Server.
466fn boolean(byte: u8) -> Value {
467    if SqlServer.booleans_are_integers() {
468        Value::Bool(byte != 0)
469    } else {
470        Value::Int(byte as i64)
471    }
472}
473
474fn fixed<const N: usize>(bytes: &[u8]) -> Result<[u8; N]> {
475    bytes
476        .get(..N)
477        .and_then(|slice| slice.try_into().ok())
478        .ok_or_else(|| Error::Protocol(format!("expected {N} bytes for a fixed-width value")))
479}
480
481fn width(name: &str, got: usize) -> Error {
482    Error::Protocol(format!("a {name} column arrived with an impossible width of {got} bytes"))
483}
484
485/// `money` is stored as an i64 of ten-thousandths, with its two halves swapped.
486fn money(bytes: &[u8]) -> Result<String> {
487    let units: i64 = match bytes.len() {
488        4 => i32::from_le_bytes(fixed(bytes)?) as i64,
489        8 => {
490            let high = i32::from_le_bytes(fixed::<4>(&bytes[..4])?) as i64;
491            let low = u32::from_le_bytes(fixed::<4>(&bytes[4..])?) as i64;
492            (high << 32) | low
493        }
494        other => return Err(width("money", other)),
495    };
496
497    Ok(scaled(units as i128, 4))
498}
499
500/// `decimal` and `numeric`: a sign byte, then an unsigned little-endian
501/// magnitude of 4, 8, 12 or 16 bytes, read against the column's scale.
502fn decimal(bytes: &[u8], scale: u8) -> Result<String> {
503    if bytes.is_empty() {
504        return Err(Error::Protocol("a decimal value arrived with no sign byte".into()));
505    }
506
507    let positive = bytes[0] == 1;
508    let mut magnitude: i128 = 0;
509    for (index, byte) in bytes[1..].iter().enumerate() {
510        if index >= 16 {
511            return Err(width("decimal", bytes.len()));
512        }
513        magnitude |= (*byte as i128) << (index * 8);
514    }
515
516    Ok(scaled(if positive { magnitude } else { -magnitude }, scale))
517}
518
519/// Render an integer count of 10^-scale units as a decimal string.
520fn scaled(units: i128, scale: u8) -> String {
521    if scale == 0 {
522        return units.to_string();
523    }
524
525    let divisor = 10i128.pow(scale as u32);
526    let sign = if units < 0 { "-" } else { "" };
527    let magnitude = units.unsigned_abs();
528
529    format!(
530        "{sign}{}.{:0width$}",
531        magnitude / divisor as u128,
532        magnitude % divisor as u128,
533        width = scale as usize
534    )
535}
536
537/// `uniqueidentifier` is a Microsoft GUID: the first three groups are stored
538/// little-endian and the last two big-endian, which is why the bytes cannot
539/// simply be printed in order.
540fn uuid(bytes: &[u8]) -> Result<String> {
541    if bytes.len() != 16 {
542        return Err(width("uniqueidentifier", bytes.len()));
543    }
544
545    Ok(format!(
546        "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{}",
547        bytes[3],
548        bytes[2],
549        bytes[1],
550        bytes[0],
551        bytes[5],
552        bytes[4],
553        bytes[7],
554        bytes[6],
555        bytes[8],
556        bytes[9],
557        bytes[10..].iter().map(|b| format!("{b:02X}")).collect::<String>()
558    ))
559}
560
561/// Decode a non-Unicode character column.
562///
563/// The bytes are in whatever code page the collation names, and the framework
564/// speaks UTF-8. A `_UTF8` collation is already UTF-8; anything else is decoded
565/// as Windows-1252, which is the code page behind every default SQL Server
566/// collation and is a superset of Latin-1. Well-formed UTF-8 is preferred
567/// whatever the collation says, because a UTF-8 column read through a legacy
568/// collation is the more common mistake of the two.
569fn decode_char(collation: Option<Collation>, bytes: &[u8]) -> String {
570    if collation.is_some_and(|c| c.is_utf8()) {
571        return String::from_utf8_lossy(bytes).into_owned();
572    }
573    match std::str::from_utf8(bytes) {
574        Ok(text) => text.to_string(),
575        Err(_) => bytes.iter().map(|byte| windows_1252(*byte)).collect(),
576    }
577}
578
579/// The 0x80–0x9F range is where Windows-1252 differs from Latin-1; everything
580/// else maps straight onto the code point of the same number.
581fn windows_1252(byte: u8) -> char {
582    const HIGH: [char; 32] = [
583        '€', '\u{81}', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', '\u{8D}', 'Ž',
584        '\u{8F}', '\u{90}', '‘', '’', '“', '”', '•', '–', '—', '˜', '™', 'š', '›', 'œ', '\u{9D}',
585        'ž', 'Ÿ',
586    ];
587
588    match byte {
589        0x80..=0x9F => HIGH[(byte - 0x80) as usize],
590        other => other as char,
591    }
592}
593
594// --- Dates and times ---
595
596/// The number of days from 0001-01-01, where SQL Server's `date` starts, to
597/// 1970-01-01, where the civil-date arithmetic below starts.
598const DAYS_0001_TO_1970: i64 = 719_162;
599
600/// The number of days from 1900-01-01, where the legacy `datetime` starts.
601const DAYS_1900_TO_1970: i64 = 25_567;
602
603fn days(bytes: &[u8]) -> i64 {
604    let mut total = 0i64;
605    for (index, byte) in bytes.iter().take(3).enumerate() {
606        total |= (*byte as i64) << (index * 8);
607    }
608    total
609}
610
611/// The count of 10^-scale second increments since midnight, from 3, 4 or 5 bytes.
612fn time_units(bytes: &[u8]) -> u64 {
613    let mut total = 0u64;
614    for (index, byte) in bytes.iter().enumerate() {
615        total |= (*byte as u64) << (index * 8);
616    }
617    total
618}
619
620/// Howard Hinnant's civil-from-days, which is exact for the proleptic
621/// Gregorian calendar SQL Server uses and needs no lookup tables.
622fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) {
623    let z = days_since_epoch + 719_468;
624    let era = z.div_euclid(146_097);
625    let day_of_era = z.rem_euclid(146_097);
626    let year_of_era =
627        (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
628    let year = year_of_era + era * 400;
629    let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
630    let month_prime = (5 * day_of_year + 2) / 153;
631    let day = (day_of_year - (153 * month_prime + 2) / 5 + 1) as u32;
632    let month = if month_prime < 10 { month_prime + 3 } else { month_prime - 9 } as u32;
633
634    (if month <= 2 { year + 1 } else { year }, month, day)
635}
636
637fn date_text(day_count: i64) -> String {
638    let (year, month, day) = civil_from_days(day_count - DAYS_0001_TO_1970);
639    format!("{year:04}-{month:02}-{day:02}")
640}
641
642fn time_text(units: u64, scale: u8) -> String {
643    let per_second = 10u64.pow(scale as u32);
644    let seconds = units / per_second;
645    let fraction = units % per_second;
646
647    let base = format!(
648        "{:02}:{:02}:{:02}",
649        seconds / 3600,
650        (seconds / 60) % 60,
651        seconds % 60
652    );
653
654    if scale == 0 {
655        base
656    } else {
657        format!("{base}.{fraction:0width$}", width = scale as usize)
658    }
659}
660
661/// The number of bytes a `time` of a given scale occupies.
662fn time_width(scale: u8) -> usize {
663    match scale {
664        0..=2 => 3,
665        3..=4 => 4,
666        _ => 5,
667    }
668}
669
670fn datetime2_text(bytes: &[u8], scale: u8) -> Result<String> {
671    let split = time_width(scale);
672    if bytes.len() < split + 3 {
673        return Err(width("datetime2", bytes.len()));
674    }
675
676    Ok(format!(
677        "{} {}",
678        date_text(days(&bytes[split..])),
679        time_text(time_units(&bytes[..split]), scale)
680    ))
681}
682
683fn datetimeoffset_text(bytes: &[u8], scale: u8) -> Result<String> {
684    let time_len = time_width(scale);
685    let split = time_len + 3;
686    if bytes.len() < split + 2 {
687        return Err(width("datetimeoffset", bytes.len()));
688    }
689
690    // The date and time on the wire are UTC; the trailing offset says which
691    // local time they were written as. SQL Server renders the *local* time, so
692    // the offset is applied before formatting — and applying it can cross
693    // midnight, which is why the day count moves with it.
694    let offset = i16::from_le_bytes([bytes[split], bytes[split + 1]]) as i64;
695    let per_second = 10i64.pow(scale as u32);
696    let day = per_second * 86_400;
697
698    let shifted = time_units(&bytes[..time_len]) as i64 + offset * 60 * per_second;
699    let local_day = days(&bytes[time_len..split]) + shifted.div_euclid(day);
700    let local_time = shifted.rem_euclid(day) as u64;
701
702    let sign = if offset < 0 { '-' } else { '+' };
703    let minutes = offset.unsigned_abs();
704
705    Ok(format!(
706        "{} {} {sign}{:02}:{:02}",
707        date_text(local_day),
708        time_text(local_time, scale),
709        minutes / 60,
710        minutes % 60
711    ))
712}
713
714/// The two pre-2008 types: `datetime` counts three-hundredths of a second, and
715/// `smalldatetime` counts whole minutes.
716fn legacy_datetime_text(bytes: &[u8]) -> Result<String> {
717    match bytes.len() {
718        4 => {
719            let day = u16::from_le_bytes(fixed::<2>(&bytes[..2])?) as i64;
720            let minutes = u16::from_le_bytes(fixed::<2>(&bytes[2..])?) as u64;
721            Ok(format!(
722                "{} {}",
723                date_text(day + DAYS_0001_TO_1970 - DAYS_1900_TO_1970),
724                time_text(minutes * 60, 0)
725            ))
726        }
727        8 => {
728            let day = i32::from_le_bytes(fixed::<4>(&bytes[..4])?) as i64;
729            let ticks = u32::from_le_bytes(fixed::<4>(&bytes[4..])?) as u64;
730            // 300 ticks a second, rendered at millisecond precision the way
731            // `select` renders it.
732            let milliseconds = ticks * 1000 / 300;
733            Ok(format!(
734                "{} {}",
735                date_text(day + DAYS_0001_TO_1970 - DAYS_1900_TO_1970),
736                time_text(milliseconds, 3)
737            ))
738        }
739        other => Err(width("datetime", other)),
740    }
741}
742
743/// Decode a `sql_variant`: a base type, its property bytes, then the value.
744///
745/// Only the scalar base types are decoded. Anything else keeps the stream
746/// aligned — the bytes have already been consumed by the caller — and comes
747/// back as NULL rather than as a wrong value.
748fn variant(bytes: &[u8]) -> Result<Value> {
749    if bytes.len() < 2 {
750        return Ok(Value::Null);
751    }
752
753    let kind = bytes[0];
754    let properties = bytes[1] as usize;
755    if bytes.len() < 2 + properties {
756        return Ok(Value::Null);
757    }
758    let (properties, data) = (&bytes[2..2 + properties], &bytes[2 + properties..]);
759
760    let type_info = match kind {
761        BITTYPE | INT1TYPE | INT2TYPE | INT4TYPE | INT8TYPE | FLT4TYPE | FLT8TYPE | MONEYTYPE
762        | MONEY4TYPE | GUIDTYPE | DATETIMETYPE | DATETIM4TYPE => {
763            TypeInfo::fixed(kind, data.len())
764        }
765        DECIMALNTYPE | NUMERICNTYPE if properties.len() >= 2 => TypeInfo {
766            kind,
767            size: data.len(),
768            precision: properties[0],
769            scale: properties[1],
770            collation: None,
771            length_style: LengthStyle::Byte,
772        },
773        BIGVARCHARTYPE | BIGCHARTYPE if properties.len() >= 5 => TypeInfo {
774            kind,
775            size: data.len(),
776            precision: 0,
777            scale: 0,
778            collation: Some(Collation::parse(properties)),
779            length_style: LengthStyle::Short,
780        },
781        NVARCHARTYPE | NCHARTYPE => TypeInfo::with_style(kind, data.len(), LengthStyle::Short),
782        BIGVARBINARYTYPE | BIGBINARYTYPE => {
783            TypeInfo::with_style(kind, data.len(), LengthStyle::Short)
784        }
785        DATENTYPE => TypeInfo::with_style(kind, 3, LengthStyle::Byte),
786        _ => return Ok(Value::Null),
787    };
788
789    decode(&type_info, data)
790}
791
792// --- Bound parameters ---
793
794/// The type each [`Value`] is declared as in the `sp_executesql` parameter list.
795///
796/// Widest-of-its-kind on purpose: an `int` column accepts a `bigint` parameter,
797/// but a `bigint` column would silently truncate an `int` one.
798fn declared_type(value: &Value) -> &'static str {
799    match value {
800        Value::Bool(_) => "bit",
801        Value::Int(_) => "bigint",
802        Value::Float(_) => "float",
803        Value::Bytes(_) => "varbinary(max)",
804        // A typed NULL has to be *some* type; `nvarchar` converts implicitly to
805        // every other one, so it is the safe choice for a value with no type.
806        Value::Null | Value::Text(_) | Value::Json(_) => "nvarchar(max)",
807    }
808}
809
810/// The `@params` argument of `sp_executesql`: `@P1 bigint, @P2 nvarchar(max)`.
811pub fn declare(params: &[Value]) -> String {
812    params
813        .iter()
814        .enumerate()
815        .map(|(index, value)| format!("@P{} {}", index + 1, declared_type(value)))
816        .collect::<Vec<_>>()
817        .join(", ")
818}
819
820/// Encode one bound parameter as TYPE_INFO followed by its value.
821pub fn encode(value: &Value) -> Vec<u8> {
822    match value {
823        Value::Bool(flag) => vec![BITNTYPE, 1, 1, u8::from(*flag)],
824
825        Value::Int(number) => {
826            let mut out = vec![INTNTYPE, 8, 8];
827            out.extend_from_slice(&number.to_le_bytes());
828            out
829        }
830
831        Value::Float(number) => {
832            let mut out = vec![FLTNTYPE, 8, 8];
833            out.extend_from_slice(&number.to_le_bytes());
834            out
835        }
836
837        Value::Bytes(bytes) => {
838            let mut out = vec![BIGVARBINARYTYPE];
839            out.extend_from_slice(&(MAX_LENGTH as u16).to_le_bytes());
840            out.extend_from_slice(&chunked(Some(bytes)));
841            out
842        }
843
844        Value::Null => nvarchar_max(None),
845        Value::Text(text) => nvarchar_max(Some(&utf16(text))),
846        Value::Json(json) => nvarchar_max(Some(&utf16(&json.to_string()))),
847    }
848}
849
850fn nvarchar_max(bytes: Option<&[u8]>) -> Vec<u8> {
851    let mut out = vec![NVARCHARTYPE];
852    out.extend_from_slice(&(MAX_LENGTH as u16).to_le_bytes());
853    // Five zero collation bytes: the server's own collation applies, which is
854    // what an application means when it binds a string.
855    out.extend_from_slice(&[0u8; 5]);
856    out.extend_from_slice(&chunked(bytes));
857    out
858}
859
860fn utf16(text: &str) -> Vec<u8> {
861    text.encode_utf16().flat_map(u16::to_le_bytes).collect()
862}
863
864/// Write a value in the PLP form every `(max)` parameter uses.
865fn chunked(bytes: Option<&[u8]>) -> Vec<u8> {
866    let Some(bytes) = bytes else {
867        return 0xFFFF_FFFF_FFFF_FFFFu64.to_le_bytes().to_vec();
868    };
869
870    let mut out = Vec::with_capacity(bytes.len() + 16);
871    out.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
872    if !bytes.is_empty() {
873        out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
874        out.extend_from_slice(bytes);
875    }
876    out.extend_from_slice(&0u32.to_le_bytes()); // terminator
877    out
878}
879
880/// A JSON document read back out of an `nvarchar(max)` column.
881///
882/// SQL Server has no JSON type, so nothing on the wire says a column holds one.
883/// This exists for callers that know it does.
884pub fn as_json(value: &Value) -> Option<Json> {
885    match value {
886        Value::Text(text) => Json::parse(text).ok(),
887        _ => None,
888    }
889}
890
891#[cfg(test)]
892mod tests {
893    use super::*;
894
895    /// Read a value the way the token stream does: a TYPE_INFO, then bytes.
896    fn read(type_info_and_value: &[u8]) -> Value {
897        let mut reader = Reader::new(type_info_and_value);
898        let type_info = parse_type_info(&mut reader).expect("a type this driver knows");
899        read_value(&mut reader, &type_info).expect("a decodable value")
900    }
901
902    #[test]
903    fn decodes_every_width_of_integer() {
904        assert_eq!(read(&[INTNTYPE, 1, 1, 7]), Value::Int(7));
905        assert_eq!(read(&[INTNTYPE, 2, 2, 0xFF, 0xFF]), Value::Int(-1));
906        assert_eq!(read(&[INTNTYPE, 4, 4, 0x2A, 0, 0, 0]), Value::Int(42));
907        assert_eq!(
908            read(&[INTNTYPE, 8, 8, 0x00, 0x1A, 0x71, 0x18, 0x02, 0, 0, 0]),
909            Value::Int(9_000_000_000)
910        );
911        // A fixed-width int4 has no length byte at all.
912        assert_eq!(read(&[INT4TYPE, 5, 0, 0, 0]), Value::Int(5));
913    }
914
915    #[test]
916    fn a_bit_column_comes_back_as_a_boolean_because_the_dialect_says_so() {
917        // SQL Server stores booleans as integers; the decoder converts them
918        // back so `row.get::<bool>()` reads the same on every database.
919        assert!(SqlServer.booleans_are_integers());
920        assert_eq!(read(&[BITNTYPE, 1, 1, 1]), Value::Bool(true));
921        assert_eq!(read(&[BITNTYPE, 1, 1, 0]), Value::Bool(false));
922        assert_eq!(read(&[BITTYPE, 1]), Value::Bool(true));
923    }
924
925    #[test]
926    fn decodes_floats_at_both_widths() {
927        let mut single = vec![FLTNTYPE, 8, 4];
928        single.extend_from_slice(&1.5f32.to_le_bytes());
929        assert_eq!(read(&single), Value::Float(1.5));
930
931        let mut double = vec![FLTNTYPE, 8, 8];
932        double.extend_from_slice(&(-0.25f64).to_le_bytes());
933        assert_eq!(read(&double), Value::Float(-0.25));
934    }
935
936    #[test]
937    fn a_zero_length_value_is_null_whatever_its_type() {
938        assert_eq!(read(&[INTNTYPE, 8, 0]), Value::Null);
939        assert_eq!(read(&[BITNTYPE, 1, 0]), Value::Null);
940
941        let mut nvarchar = vec![NVARCHARTYPE, 0x40, 0x00, 0, 0, 0, 0, 0];
942        nvarchar.extend_from_slice(&0xFFFFu16.to_le_bytes());
943        assert_eq!(read(&nvarchar), Value::Null);
944    }
945
946    #[test]
947    fn decodes_an_nvarchar_and_the_max_variant_that_arrives_in_chunks() {
948        let text: Vec<u8> = "héllo".encode_utf16().flat_map(u16::to_le_bytes).collect();
949
950        let mut plain = vec![NVARCHARTYPE, 0x40, 0x00, 0, 0, 0, 0, 0];
951        plain.extend_from_slice(&(text.len() as u16).to_le_bytes());
952        plain.extend_from_slice(&text);
953        assert_eq!(read(&plain), Value::Text("héllo".into()));
954
955        // The same string as nvarchar(max), split across two chunks.
956        let mut chunked = vec![NVARCHARTYPE, 0xFF, 0xFF, 0, 0, 0, 0, 0];
957        chunked.extend_from_slice(&(text.len() as u64).to_le_bytes());
958        chunked.extend_from_slice(&4u32.to_le_bytes());
959        chunked.extend_from_slice(&text[..4]);
960        chunked.extend_from_slice(&((text.len() - 4) as u32).to_le_bytes());
961        chunked.extend_from_slice(&text[4..]);
962        chunked.extend_from_slice(&0u32.to_le_bytes());
963        assert_eq!(read(&chunked), Value::Text("héllo".into()));
964    }
965
966    #[test]
967    fn a_varchar_is_decoded_against_its_collation() {
968        // A Windows-1252 byte that is not valid UTF-8.
969        let mut latin = vec![BIGVARCHARTYPE, 0x40, 0x00, 0x09, 0x04, 0xD0, 0x00, 0x34];
970        latin.extend_from_slice(&2u16.to_le_bytes());
971        latin.extend_from_slice(&[b'a', 0xE9]);
972        assert_eq!(read(&latin), Value::Text("aé".into()));
973
974        // The same bytes under a UTF-8 collation, where fUTF8 is set.
975        let mut utf8 = vec![BIGVARCHARTYPE, 0x40, 0x00, 0x09, 0x04, 0x00, 0x04, 0x34];
976        let bytes = "aé".as_bytes();
977        utf8.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
978        utf8.extend_from_slice(bytes);
979        assert_eq!(read(&utf8), Value::Text("aé".into()));
980    }
981
982    #[test]
983    fn decodes_binary_including_the_max_variant() {
984        let mut plain = vec![BIGVARBINARYTYPE, 0x10, 0x00];
985        plain.extend_from_slice(&4u16.to_le_bytes());
986        plain.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
987        assert_eq!(read(&plain), Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF]));
988
989        let mut max = vec![BIGVARBINARYTYPE, 0xFF, 0xFF];
990        max.extend_from_slice(&2u64.to_le_bytes());
991        max.extend_from_slice(&2u32.to_le_bytes());
992        max.extend_from_slice(&[0x01, 0x02]);
993        max.extend_from_slice(&0u32.to_le_bytes());
994        assert_eq!(read(&max), Value::Bytes(vec![0x01, 0x02]));
995    }
996
997    #[test]
998    fn decimals_stay_text_so_the_precision_they_exist_for_survives() {
999        // 12345678901234567890 at scale 6 → 12345678901234.567890
1000        let mut bytes = vec![DECIMALNTYPE, 17, 38, 6, 17, 1];
1001        let mut magnitude = 12_345_678_901_234_567_890u128.to_le_bytes().to_vec();
1002        magnitude.truncate(16);
1003        bytes.extend_from_slice(&magnitude);
1004        assert_eq!(read(&bytes), Value::Text("12345678901234.567890".into()));
1005
1006        // Negative, and with a scale that needs zero padding.
1007        let mut negative = vec![DECIMALNTYPE, 5, 10, 4, 5, 0];
1008        negative.extend_from_slice(&25u32.to_le_bytes());
1009        assert_eq!(read(&negative), Value::Text("-0.0025".into()));
1010    }
1011
1012    #[test]
1013    fn money_keeps_its_four_decimal_places() {
1014        let mut bytes = vec![MONEYNTYPE, 8, 8];
1015        // 12.3400 → 123400 ten-thousandths, high half then low half.
1016        bytes.extend_from_slice(&0i32.to_le_bytes());
1017        bytes.extend_from_slice(&123_400u32.to_le_bytes());
1018        assert_eq!(read(&bytes), Value::Text("12.3400".into()));
1019    }
1020
1021    #[test]
1022    fn a_uniqueidentifier_swaps_the_first_three_groups() {
1023        let mut bytes = vec![GUIDTYPE, 16, 16];
1024        bytes.extend_from_slice(&[
1025            0x78, 0x56, 0x34, 0x12, 0x34, 0x12, 0x78, 0x56, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34,
1026            0x56, 0x78,
1027        ]);
1028
1029        assert_eq!(
1030            read(&bytes),
1031            Value::Text("12345678-1234-5678-9ABC-DEF012345678".into())
1032        );
1033    }
1034
1035    #[test]
1036    fn dates_and_times_come_back_as_iso_text() {
1037        // 2026-08-29 is 739_856 days after 0001-01-01.
1038        let day = 739_856u32.to_le_bytes();
1039        assert_eq!(
1040            read(&[DATENTYPE, 3, day[0], day[1], day[2]]),
1041            Value::Text("2026-08-29".into())
1042        );
1043
1044        // 10:30:00 at scale 7 is 10 * 3600 + 30 * 60 seconds of ten-millionths.
1045        let units = (37_800u64 * 10_000_000).to_le_bytes();
1046        let mut time = vec![TIMENTYPE, 7, 5];
1047        time.extend_from_slice(&units[..5]);
1048        assert_eq!(read(&time), Value::Text("10:30:00.0000000".into()));
1049    }
1050
1051    #[test]
1052    fn a_datetime2_puts_the_time_before_the_date_on_the_wire() {
1053        let mut bytes = vec![DATETIME2NTYPE, 3, 7];
1054        // 01:02:03.004 at scale 3, then 2026-08-29.
1055        let units = (3_723_004u64).to_le_bytes();
1056        bytes.extend_from_slice(&units[..4]);
1057        bytes.extend_from_slice(&739_856u32.to_le_bytes()[..3]);
1058
1059        assert_eq!(read(&bytes), Value::Text("2026-08-29 01:02:03.004".into()));
1060    }
1061
1062    #[test]
1063    fn a_datetimeoffset_is_rendered_in_the_zone_it_was_written_in() {
1064        // The wire carries UTC — 06:32:03 — and an offset of −05:30, so the
1065        // local time the column was written as is 01:02:03.
1066        let mut bytes = vec![DATETIMEOFFSETNTYPE, 0, 8];
1067        bytes.extend_from_slice(&23_523u64.to_le_bytes()[..3]);
1068        bytes.extend_from_slice(&739_856u32.to_le_bytes()[..3]);
1069        bytes.extend_from_slice(&(-330i16).to_le_bytes());
1070
1071        assert_eq!(read(&bytes), Value::Text("2026-08-29 01:02:03 -05:30".into()));
1072    }
1073
1074    #[test]
1075    fn a_datetimeoffset_that_crosses_midnight_moves_the_date_with_it() {
1076        // 00:30:00 UTC at −05:30 is half past seven the previous evening.
1077        let mut bytes = vec![DATETIMEOFFSETNTYPE, 0, 8];
1078        bytes.extend_from_slice(&1_800u64.to_le_bytes()[..3]);
1079        bytes.extend_from_slice(&739_856u32.to_le_bytes()[..3]);
1080        bytes.extend_from_slice(&(-330i16).to_le_bytes());
1081
1082        assert_eq!(read(&bytes), Value::Text("2026-08-28 19:00:00 -05:30".into()));
1083    }
1084
1085    #[test]
1086    fn the_legacy_datetime_types_are_decoded_too() {
1087        // `datetime`: days since 1900-01-01 and three-hundredths of a second.
1088        let mut long = vec![DATETIMNTYPE, 8, 8];
1089        long.extend_from_slice(&46_261i32.to_le_bytes());
1090        long.extend_from_slice(&(300u32 * 3600).to_le_bytes());
1091        assert_eq!(read(&long), Value::Text("2026-08-29 01:00:00.000".into()));
1092
1093        // `smalldatetime`: days and whole minutes.
1094        let mut short = vec![DATETIMNTYPE, 8, 4];
1095        short.extend_from_slice(&46_261u16.to_le_bytes());
1096        short.extend_from_slice(&90u16.to_le_bytes());
1097        assert_eq!(read(&short), Value::Text("2026-08-29 01:30:00".into()));
1098    }
1099
1100    #[test]
1101    fn a_row_decodes_every_column_in_order() {
1102        let columns = vec![
1103            Column { name: "id".into(), type_info: TypeInfo::with_style(INTNTYPE, 8, LengthStyle::Byte) },
1104            Column {
1105                name: "flag".into(),
1106                type_info: TypeInfo::with_style(BITNTYPE, 1, LengthStyle::Byte),
1107            },
1108        ];
1109
1110        let mut body = vec![8];
1111        body.extend_from_slice(&7i64.to_le_bytes());
1112        body.extend_from_slice(&[1, 1]);
1113
1114        let values = read_row(&mut Reader::new(&body), &columns).unwrap();
1115        assert_eq!(values, vec![Value::Int(7), Value::Bool(true)]);
1116    }
1117
1118    #[test]
1119    fn an_nbcrow_reads_its_null_bitmap_and_skips_the_columns_it_marks() {
1120        // Nine columns so the bitmap needs two bytes; the second and the ninth
1121        // are NULL and therefore send no bytes at all.
1122        let columns: Vec<Column> = (0..9)
1123            .map(|index| Column {
1124                name: format!("c{index}"),
1125                type_info: TypeInfo::with_style(INTNTYPE, 8, LengthStyle::Byte),
1126            })
1127            .collect();
1128
1129        let mut body = vec![0b0000_0010, 0b0000_0001];
1130        for value in [0i64, 2, 3, 4, 5, 6, 7] {
1131            body.push(8);
1132            body.extend_from_slice(&value.to_le_bytes());
1133        }
1134
1135        let values = read_nbc_row(&mut Reader::new(&body), &columns).unwrap();
1136
1137        assert_eq!(values.len(), 9);
1138        assert_eq!(values[1], Value::Null);
1139        assert_eq!(values[8], Value::Null);
1140        assert_eq!(values[0], Value::Int(0));
1141        assert_eq!(values[7], Value::Int(7));
1142    }
1143
1144    #[test]
1145    fn column_metadata_names_every_column_and_its_type() {
1146        let mut body = Vec::new();
1147        body.extend_from_slice(&2u16.to_le_bytes());
1148
1149        body.extend_from_slice(&0u32.to_le_bytes()); // user type
1150        body.extend_from_slice(&0u16.to_le_bytes()); // flags
1151        body.extend_from_slice(&[INTNTYPE, 8]);
1152        body.push(2);
1153        body.extend("id".encode_utf16().flat_map(u16::to_le_bytes));
1154
1155        body.extend_from_slice(&0u32.to_le_bytes());
1156        body.extend_from_slice(&0u16.to_le_bytes());
1157        body.extend_from_slice(&[NVARCHARTYPE, 0xFF, 0xFF, 0, 0, 0, 0, 0]);
1158        body.push(4);
1159        body.extend("name".encode_utf16().flat_map(u16::to_le_bytes));
1160
1161        let columns = parse_column_metadata(&mut Reader::new(&body)).unwrap();
1162
1163        assert_eq!(columns.len(), 2);
1164        assert_eq!(columns[0].name, "id");
1165        assert_eq!(columns[1].name, "name");
1166        // nvarchar(max) arrives in chunks, not with a plain length.
1167        assert_eq!(columns[1].type_info.length_style, LengthStyle::Chunked);
1168    }
1169
1170    #[test]
1171    fn metadata_for_a_statement_that_returns_nothing_has_no_columns() {
1172        let body = 0xFFFFu16.to_le_bytes();
1173        assert!(parse_column_metadata(&mut Reader::new(&body)).unwrap().is_empty());
1174    }
1175
1176    #[test]
1177    fn a_type_this_driver_cannot_decode_is_named_in_the_error() {
1178        let error = parse_type_info(&mut Reader::new(&[0xF0])).unwrap_err().to_string();
1179        assert!(error.contains("0xF0"), "{error}");
1180    }
1181
1182    #[test]
1183    fn parameters_are_declared_with_the_widest_type_of_their_kind() {
1184        let declaration = declare(&[
1185            Value::Int(1),
1186            Value::Text("a".into()),
1187            Value::Bool(true),
1188            Value::Float(1.0),
1189            Value::Bytes(vec![1]),
1190            Value::Null,
1191        ]);
1192
1193        assert_eq!(
1194            declaration,
1195            "@P1 bigint, @P2 nvarchar(max), @P3 bit, @P4 float, \
1196             @P5 varbinary(max), @P6 nvarchar(max)"
1197        );
1198        assert_eq!(declare(&[]), "");
1199    }
1200
1201    #[test]
1202    fn an_encoded_parameter_round_trips_back_through_the_decoder() {
1203        // Whatever the encoder writes, the decoder must read: that is what
1204        // proves a bound value survives the trip unchanged.
1205        for value in [
1206            Value::Int(-9_000_000_000),
1207            Value::Bool(true),
1208            Value::Bool(false),
1209            Value::Float(1.5),
1210            Value::Text("'; drop table users; --".into()),
1211            Value::Text(String::new()),
1212            Value::Bytes(vec![0xDE, 0xAD]),
1213            Value::Null,
1214        ] {
1215            let encoded = encode(&value);
1216            let mut reader = Reader::new(&encoded);
1217            let type_info = parse_type_info(&mut reader).unwrap();
1218            let decoded = read_value(&mut reader, &type_info).unwrap();
1219
1220            assert_eq!(decoded, value, "{value:?} did not survive encoding");
1221            assert!(reader.is_empty(), "{value:?} left bytes behind");
1222        }
1223    }
1224
1225    #[test]
1226    fn a_null_parameter_is_sent_as_a_typed_null_not_as_an_empty_string() {
1227        let encoded = encode(&Value::Null);
1228
1229        assert_eq!(encoded[0], NVARCHARTYPE);
1230        // The PLP length is the all-ones NULL marker, and no chunks follow.
1231        assert_eq!(&encoded[encoded.len() - 8..], &[0xFF; 8]);
1232    }
1233
1234    #[test]
1235    fn json_is_bound_as_the_nvarchar_sql_server_stores_it_in() {
1236        let json = Json::parse(r#"{"a":1}"#).unwrap();
1237        let encoded = encode(&Value::from(json.clone()));
1238
1239        assert_eq!(encoded[0], NVARCHARTYPE);
1240        match read(&encoded) {
1241            Value::Text(text) => assert_eq!(as_json(&Value::Text(text)).unwrap(), json),
1242            other => panic!("expected text, got {other:?}"),
1243        }
1244    }
1245
1246    #[test]
1247    fn civil_dates_are_exact_across_leap_years_and_centuries() {
1248        assert_eq!(civil_from_days(0), (1970, 1, 1));
1249        assert_eq!(civil_from_days(-1), (1969, 12, 31));
1250
1251        // 2000 was a leap year and 1900 was not, which is the pair of cases a
1252        // hand-written calendar gets wrong.
1253        assert_eq!(date_text(DAYS_0001_TO_1970 + 11_016), "2000-02-29");
1254        assert_eq!(date_text(DAYS_0001_TO_1970 - 25_508), "1900-03-01");
1255        // Day zero of the `date` type, which nothing else in the driver reaches.
1256        assert_eq!(date_text(0), "0001-01-01");
1257    }
1258}