Skip to main content

tds_driver/tds/codec/
column_data.rs

1mod binary;
2mod bit;
3mod bytes_mut_with_type_info;
4#[cfg(feature = "tds73")]
5mod date;
6#[cfg(feature = "tds73")]
7mod datetime2;
8mod datetimen;
9#[cfg(feature = "tds73")]
10mod datetimeoffsetn;
11mod fixed_len;
12mod float;
13mod guid;
14mod image;
15mod int;
16mod money;
17mod plp;
18mod sql_variant;
19mod string;
20mod text;
21#[cfg(feature = "tds73")]
22mod time;
23mod udt;
24mod var_len;
25mod xml;
26
27/// Upper bound on how many bytes a value decoder will *pre-allocate* from a
28/// server-supplied length field before it has read the corresponding data.
29///
30/// The wire length is untrusted: a malformed or hostile server can claim a
31/// value is up to `u32::MAX`/`u64::MAX` bytes long. Reserving that up front is a
32/// memory-exhaustion vector (and, for `u64` lengths, can even exceed `Vec`'s
33/// `isize::MAX` capacity limit and panic). Decoders therefore cap the initial
34/// reservation to this value and let the buffer grow as bytes actually arrive;
35/// a short/lying length still fails cleanly when the read runs out of input.
36pub(crate) const MAX_PREALLOC: usize = 8192; // 8 KiB
37
38/// Absolute ceiling on the *total* size of a single PLP (partially
39/// length-prefixed) value — `varchar(max)`, `nvarchar(max)`, `varbinary(max)`,
40/// `xml`, and CLR UDTs. SQL Server's own MAX types top out at `2^31 - 1` bytes,
41/// so any value that would grow past this is malformed. Without this bound the
42/// "unknown length" PLP form (which streams an arbitrary number of chunks until
43/// a zero-length terminator) lets a hostile server grow the accumulation buffer
44/// without limit and OOM the client on a single column value.
45pub(crate) const MAX_PLP_SIZE: usize = i32::MAX as usize;
46
47use super::{Encode, FixedLenType, TypeInfo, VarLenContext, VarLenType};
48#[cfg(feature = "tds73")]
49use crate::tds::time::{Date, DateTime2, DateTimeOffset, Time};
50use crate::{
51    tds::{time::DateTime, time::SmallDateTime, xml::XmlData, Numeric},
52    FromSql, FromSqlOwned, IntoSql, SqlReadBytes, ToSql,
53};
54use bytes::{BufMut, BytesMut};
55pub(crate) use bytes_mut_with_type_info::BytesMutWithTypeInfo;
56use std::borrow::{BorrowMut, Cow};
57use uuid::Uuid;
58
59/// UTF-16 code units in the 2^31 - 1 bytes of nvarchar(max), xml and ntext.
60const NVARCHAR_MAX_UNITS: usize = i32::MAX as usize / 2;
61
62/// Precision of every numeric parameter: the server's maximum.
63const NUMERIC_PARAM_PRECISION: u8 = 38;
64
65/// Whether `s` fits `nvarchar(4000)`, whose limit is in UTF-16 code units.
66/// A UTF-8 byte is at most one unit and a unit at most three bytes, so only
67/// strings of 4001..=12000 bytes need counting.
68fn fits_nvarchar_4000(s: &str) -> bool {
69    s.len() <= 4000 || (s.len() <= 12000 && s.encode_utf16().count() <= 4000)
70}
71
72const PLP_NULL: u64 = 0xffffffffffffffff;
73
74/// `s`'s UTF-16 length if over `max` units. A unit is at least one UTF-8
75/// byte, so only longer strings are counted.
76fn utf16_units_over(s: &str, max: usize) -> Option<usize> {
77    if s.len() <= max {
78        return None;
79    }
80    Some(s.encode_utf16().count()).filter(|&units| units > max)
81}
82
83/// `len` as the length of a (max), text, ntext or image value, which holds
84/// at most 2^31 - 1 bytes.
85fn lob_len(len: usize) -> Option<u32> {
86    i32::try_from(len).ok().map(i32::unsigned_abs)
87}
88
89/// An nvarchar(max) or xml value: UTF-16 in one PLP chunk, of a total
90/// length not known before encoding.
91pub(crate) fn put_plp_utf16(dst: &mut BytesMut, s: &str) -> crate::Result<()> {
92    let too_long = |units: usize| {
93        crate::Error::Conversion(
94            format!(
95                "a string of {units} UTF-16 code units is longer than nvarchar(max) and xml \
96                 hold ({NVARCHAR_MAX_UNITS})"
97            )
98            .into(),
99        )
100    };
101    // Before writing what can be gigabytes.
102    if let Some(units) = utf16_units_over(s, NVARCHAR_MAX_UNITS) {
103        return Err(too_long(units));
104    }
105
106    dst.put_u64_le(0xfffffffffffffffe); // unknown length
107    let len_pos = dst.len();
108    dst.put_u32_le(0);
109    for unit in s.encode_utf16() {
110        dst.put_u16_le(unit);
111    }
112
113    let bytes = dst.len() - len_pos - 4;
114    let len = lob_len(bytes).ok_or_else(|| too_long(bytes / 2))?;
115    dst[len_pos..len_pos + 4].copy_from_slice(&len.to_le_bytes());
116
117    // A zero-length chunk is the terminator: an empty value is that alone.
118    if len > 0 {
119        dst.put_u32_le(0);
120    }
121
122    Ok(())
123}
124
125/// `nvarchar(max)` TYPE_INFO and value.
126fn put_nvarchar_max(dst: &mut BytesMut, s: Option<&str>) -> crate::Result<()> {
127    // length: 0xffff and raw collation
128    dst.put_u8(VarLenType::NVarchar as u8);
129    dst.extend_from_slice(&[0xff_u8; 2]);
130    dst.extend_from_slice(&[0u8; 5]);
131
132    match s {
133        Some(s) => put_plp_utf16(dst, s),
134        None => {
135            dst.put_u64_le(PLP_NULL);
136            Ok(())
137        }
138    }
139}
140
141/// `varbinary(max)` TYPE_INFO and value.
142fn put_varbinary_max(dst: &mut BytesMut, bytes: Option<&[u8]>) -> crate::Result<()> {
143    dst.put_u8(VarLenType::BigVarBin as u8);
144    // Max length
145    dst.put_u16_le(0xffff_u16);
146
147    let Some(bytes) = bytes else {
148        dst.put_u64_le(PLP_NULL);
149        return Ok(());
150    };
151    let len = lob_len(bytes.len()).ok_or_else(|| {
152        crate::Error::Conversion(
153            format!(
154                "{} bytes are more than varbinary(max) holds ({})",
155                bytes.len(),
156                i32::MAX
157            )
158            .into(),
159        )
160    })?;
161
162    // Unknown length, then the value in one chunk.
163    dst.put_u64_le(0xfffffffffffffffe_u64);
164    dst.put_u32_le(len);
165
166    if len > 0 {
167        dst.extend_from_slice(bytes);
168        // PLP_TERMINATOR
169        dst.put_u32_le(0);
170    }
171
172    Ok(())
173}
174
175/// Number of days between `0001-01-01` (the `DateTime2`/`Date` epoch) and
176/// `1900-01-01` (the `datetime`/`Datetimen` epoch).
177#[cfg(feature = "tds73")]
178const DAYS_YEAR_1_TO_1900: u32 = 693_595;
179
180/// Time of day unit for converting between `datetime` (1/300 s),
181/// `smalldatetime` (minutes) and `datetime2` (10^-7 s) without rounding twice.
182const UNITS_PER_SECOND: u64 = 3_000_000_000;
183const UNITS_PER_DAY: u64 = 86_400 * UNITS_PER_SECOND;
184
185fn not_null_error() -> crate::Error {
186    crate::Error::BulkInput("NULL for a NOT NULL column".into())
187}
188
189/// Bulk/TVP integers and floats go out at the column's width: the server
190/// reads the column's type from the bytes, so a narrower value is misread.
191fn encode_number(
192    data: &ColumnData<'_>,
193    ti: &TypeInfo,
194    dst: &mut BytesMutWithTypeInfo<'_>,
195) -> Option<crate::Result<()>> {
196    let (float, width, nullable) = match ti {
197        TypeInfo::FixedLen(FixedLenType::Int1) => (false, 1, false),
198        TypeInfo::FixedLen(FixedLenType::Int2) => (false, 2, false),
199        TypeInfo::FixedLen(FixedLenType::Int4) => (false, 4, false),
200        TypeInfo::FixedLen(FixedLenType::Int8) => (false, 8, false),
201        TypeInfo::FixedLen(FixedLenType::Float4) => (true, 4, false),
202        TypeInfo::FixedLen(FixedLenType::Float8) => (true, 8, false),
203        TypeInfo::VarLenSized(vlc) if vlc.r#type() == VarLenType::Intn => (false, vlc.len(), true),
204        TypeInfo::VarLenSized(vlc) if vlc.r#type() == VarLenType::Floatn => (true, vlc.len(), true),
205        _ => return None,
206    };
207
208    let bytes = if float {
209        let value = match *data {
210            ColumnData::F32(v) => v.map(f64::from),
211            ColumnData::F64(v) => v,
212            _ => return None,
213        };
214        value.map(|v| float_bytes(v, width))
215    } else {
216        let value = match *data {
217            ColumnData::U8(v) => v.map(i64::from),
218            ColumnData::I16(v) => v.map(i64::from),
219            ColumnData::I32(v) => v.map(i64::from),
220            ColumnData::I64(v) => v,
221            _ => return None,
222        };
223        value.map(|v| int_bytes(v, width))
224    };
225
226    Some(match bytes.transpose() {
227        Err(e) => Err(e),
228        Ok(None) if nullable => {
229            dst.put_u8(0);
230            Ok(())
231        }
232        Ok(None) => Err(not_null_error()),
233        Ok(Some(bytes)) => {
234            if nullable {
235                dst.put_u8(width as u8);
236            }
237            dst.extend_from_slice(&bytes[..width]);
238            Ok(())
239        }
240    })
241}
242
243fn int_bytes(value: i64, width: usize) -> crate::Result<[u8; 8]> {
244    let (name, fits) = match width {
245        1 => ("tinyint", (0..=255).contains(&value)),
246        2 => ("smallint", i16::try_from(value).is_ok()),
247        4 => ("int", i32::try_from(value).is_ok()),
248        8 => ("bigint", true),
249        _ => {
250            return Err(crate::Error::Protocol(
251                format!("integer column of {width} bytes").into(),
252            ))
253        }
254    };
255
256    if !fits {
257        return Err(crate::Error::Conversion(
258            format!("{value} is out of range for {name}").into(),
259        ));
260    }
261
262    Ok(value.to_le_bytes())
263}
264
265fn float_bytes(value: f64, width: usize) -> crate::Result<[u8; 8]> {
266    let mut bytes = [0u8; 8];
267
268    match width {
269        8 => bytes = value.to_le_bytes(),
270        4 => {
271            let narrowed = value as f32;
272            if value.is_finite() && narrowed.is_infinite() {
273                return Err(crate::Error::Conversion(
274                    format!("{value:e} is out of range for real").into(),
275                ));
276            }
277            bytes[..4].copy_from_slice(&narrowed.to_le_bytes());
278        }
279        _ => {
280            return Err(crate::Error::Protocol(
281                format!("float column of {width} bytes").into(),
282            ))
283        }
284    }
285
286    Ok(bytes)
287}
288
289/// Bulk/TVP `datetime`/`smalldatetime` values go out in the column's own
290/// format, rounded the way SQL Server's CAST rounds them.
291fn encode_legacy_datetime(
292    data: &ColumnData<'_>,
293    ti: &TypeInfo,
294    dst: &mut BytesMutWithTypeInfo<'_>,
295) -> Option<crate::Result<()>> {
296    let (small, nullable) = match ti {
297        TypeInfo::FixedLen(FixedLenType::Datetime) => (false, false),
298        TypeInfo::FixedLen(FixedLenType::Datetime4) => (true, false),
299        TypeInfo::VarLenSized(vlc) if vlc.r#type() == VarLenType::Datetimen => {
300            (vlc.len() == 4, true)
301        }
302        _ => return None,
303    };
304
305    // Days since 1900-01-01 and the time of day in `UNITS_PER_SECOND`.
306    let parts = match *data {
307        // Validated: rounding would carry 24:00 and later into the next day.
308        ColumnData::DateTime(v) => v.map(|v| {
309            v.validate()?;
310            Ok((
311                i64::from(v.days()),
312                u64::from(v.seconds_fragments()) * (UNITS_PER_SECOND / 300),
313            ))
314        }),
315        // `seconds_fragments` of a smalldatetime counts minutes.
316        ColumnData::SmallDateTime(v) => v.map(|v| {
317            v.validate()?;
318            Ok((
319                i64::from(v.days()),
320                u64::from(v.seconds_fragments()) * 60 * UNITS_PER_SECOND,
321            ))
322        }),
323        #[cfg(feature = "tds73")]
324        ColumnData::DateTime2(v) => v.map(datetime2_parts),
325        _ => return None,
326    };
327
328    Some(match parts.transpose() {
329        Err(e) => Err(e),
330        Ok(None) if nullable => {
331            dst.put_u8(0);
332            Ok(())
333        }
334        Ok(None) => Err(not_null_error()),
335        Ok(Some((days, units))) if small => to_smalldatetime(days, units).and_then(|v| {
336            if nullable {
337                dst.put_u8(4);
338            }
339            v.encode(dst)
340        }),
341        Ok(Some((days, units))) => to_datetime(days, units).and_then(|v| {
342            if nullable {
343                dst.put_u8(8);
344            }
345            v.encode(dst)
346        }),
347    })
348}
349
350/// Rounds half up to 1/300 s, carrying into the next day.
351fn to_datetime(days: i64, units: u64) -> crate::Result<DateTime> {
352    const PER_FRAGMENT: u64 = UNITS_PER_SECOND / 300;
353    const FRAGMENTS_PER_DAY: u64 = UNITS_PER_DAY / PER_FRAGMENT;
354
355    let fragments = (units + PER_FRAGMENT / 2) / PER_FRAGMENT;
356    let days = days + (fragments / FRAGMENTS_PER_DAY) as i64;
357
358    // 1753-01-01 ..= 9999-12-31
359    if !(-53_690..=2_958_463).contains(&days) {
360        return Err(crate::Error::Conversion(
361            "value is outside the datetime range of 1753-01-01 to 9999-12-31".into(),
362        ));
363    }
364
365    Ok(DateTime::new(
366        days as i32,
367        (fragments % FRAGMENTS_PER_DAY) as u32,
368    ))
369}
370
371/// Rounds half up to the minute, carrying into the next day.
372fn to_smalldatetime(days: i64, units: u64) -> crate::Result<SmallDateTime> {
373    const PER_MINUTE: u64 = 60 * UNITS_PER_SECOND;
374
375    let minutes = (units + PER_MINUTE / 2) / PER_MINUTE;
376    let days = days + (minutes / 1440) as i64;
377
378    // 1900-01-01 ..= 2079-06-06
379    let days = u16::try_from(days).map_err(|_| {
380        crate::Error::Conversion(
381            "value is outside the smalldatetime range of 1900-01-01 to 2079-06-06".into(),
382        )
383    })?;
384
385    Ok(SmallDateTime::new(days, (minutes % 1440) as u16))
386}
387
388#[cfg(feature = "tds73")]
389fn datetime2_parts(dt2: DateTime2) -> crate::Result<(i64, u64)> {
390    let time = rescale_time(dt2.time(), 7)?;
391    let days = i64::from(dt2.date().days()) - i64::from(DAYS_YEAR_1_TO_1900);
392
393    Ok((days, time.increments() * (UNITS_PER_SECOND / 10_000_000)))
394}
395
396/// A bulk/TVP datetime2 for a datetimeoffset or date column, as SqlBulkCopy
397/// takes a DateTime for both: `new DateTimeOffset(DateTime)` in
398/// SqlParameter.CoerceValue (SqlParameter.cs:2372), and WriteDate keeps the
399/// days (TdsParser.cs:8141), dotnet/SqlClient@5394363. The offset is +00:00,
400/// as `CAST(datetime2 AS datetimeoffset)` gives and `DateTime<Utc>` means,
401/// not the local offset SqlClient gives an unspecified DateTime.
402#[cfg(feature = "tds73")]
403fn datetime2_for_column<'a>(data: ColumnData<'a>, ti: Option<&TypeInfo>) -> ColumnData<'a> {
404    match (data, ti) {
405        (ColumnData::DateTime2(v), Some(TypeInfo::VarLenSized(vlc)))
406            if vlc.r#type() == VarLenType::DatetimeOffsetn =>
407        {
408            ColumnData::DateTimeOffset(v.map(|v| DateTimeOffset::new(v, 0)))
409        }
410        (ColumnData::DateTime2(v), Some(TypeInfo::VarLenSized(vlc)))
411            if vlc.r#type() == VarLenType::Daten =>
412        {
413            ColumnData::Date(v.map(DateTime2::date))
414        }
415        (data, _) => data,
416    }
417}
418
419/// Puts `time` at the column's scale. Dropped digits are truncated, as
420/// SqlClient does for bulk copy (`value.Ticks / TICKS_FROM_SCALE[scale]` in
421/// TdsParser.WriteTime and WriteDateTime2, TdsParser.cs:8172 and :8200) and
422/// TVPs (TdsValueSetter.cs:588, :660, :694), dotnet/SqlClient@5394363.
423#[cfg(feature = "tds73")]
424fn rescale_time(time: Time, scale: u8) -> crate::Result<Time> {
425    time.validate()?;
426
427    if scale > 7 {
428        return Err(crate::Error::Conversion(
429            format!("time scale {scale} is invalid").into(),
430        ));
431    }
432
433    // Validated: under 864 * 10^9, so times 10^7 fits.
434    let increments = if scale >= time.scale() {
435        time.increments() * 10u64.pow(u32::from(scale - time.scale()))
436    } else {
437        time.increments() / 10u64.pow(u32::from(time.scale() - scale))
438    };
439
440    Ok(Time::new(increments, scale))
441}
442
443/// Puts a bulk/TVP decimal at its column's scale, rounding half away from
444/// zero as SqlDecimal.AdjustScale does, then checks it against the column's
445/// precision, as SqlBulkCopy.ConvertValue does (SqlBulkCopy.cs:1816-1823,
446/// dotnet/SqlClient@5394363): the server fails the whole insert (9803) on a
447/// value the column cannot hold.
448fn rescale_numeric(num: Numeric, precision: u8, scale: u8) -> crate::Result<Numeric> {
449    // Not `num`'s Display: a deserialized Numeric can have scale > 38.
450    let out_of_range = || {
451        crate::Error::Conversion(
452            format!(
453                "{} at scale {} is out of range for decimal({precision},{scale})",
454                num.value(),
455                num.scale()
456            )
457            .into(),
458        )
459    };
460
461    // At most 38 digits, as for parameters.
462    let magnitude = num.value().unsigned_abs();
463    if magnitude >= 10u128.pow(38) || scale > 38 {
464        return Err(out_of_range());
465    }
466
467    let magnitude = match scale.checked_sub(num.scale()) {
468        Some(up) => magnitude.checked_mul(10u128.pow(u32::from(up))),
469        None => 10u128
470            .checked_pow(u32::from(num.scale() - scale))
471            .map(|f| magnitude / f + u128::from(magnitude % f >= f / 2)),
472    }
473    .filter(|&m| m < 10u128.pow(u32::from(precision.min(38))))
474    .ok_or_else(out_of_range)?;
475
476    // Below 10^38, so within i128.
477    let value = magnitude as i128;
478    Ok(Numeric::new_with_scale(
479        if num.value() < 0 { -value } else { value },
480        scale,
481    ))
482}
483
484/// Largest value a column takes, in bytes. (max) columns declare 0xffff.
485fn column_limit(vlc: &VarLenContext) -> usize {
486    if vlc.len() < 0xffff {
487        vlc.len()
488    } else {
489        MAX_PLP_SIZE
490    }
491}
492
493/// One PLP chunk holds the whole value: `column_limit` keeps it below 2^31.
494fn put_plp(dst: &mut BytesMutWithTypeInfo<'_>, bytes: &[u8]) {
495    dst.put_u64_le(0xfffffffffffffffe); // unknown length
496    dst.put_u32_le(bytes.len() as u32);
497
498    if !bytes.is_empty() {
499        dst.extend_from_slice(bytes);
500        dst.put_u32_le(0); // terminator
501    }
502}
503
504/// Encodes in the column's code page; the server stores the bytes as is.
505fn encode_in_collation(s: &str, vlc: &VarLenContext) -> crate::Result<Vec<u8>> {
506    let collation = vlc
507        .collation()
508        .ok_or_else(|| crate::Error::BulkInput("character column without a collation".into()))?;
509    string::encode_varchar(s, &collation)
510}
511
512#[derive(Clone, Debug, PartialEq)]
513#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
514/// A container of a value that can be represented as a TDS value.
515pub enum ColumnData<'a> {
516    /// 8-bit integer, unsigned.
517    U8(Option<u8>),
518    /// 16-bit integer, signed.
519    I16(Option<i16>),
520    /// 32-bit integer, signed.
521    I32(Option<i32>),
522    /// 64-bit integer, signed.
523    I64(Option<i64>),
524    /// 32-bit floating point number.
525    F32(Option<f32>),
526    /// 64-bit floating point number.
527    F64(Option<f64>),
528    /// Boolean.
529    Bit(Option<bool>),
530    /// A string value.
531    String(Option<Cow<'a, str>>),
532    /// A Guid (UUID) value.
533    Guid(Option<Uuid>),
534    /// Binary data.
535    Binary(Option<Cow<'a, [u8]>>),
536    /// Numeric value (a decimal).
537    Numeric(Option<Numeric>),
538    /// XML data.
539    Xml(Option<Cow<'a, XmlData>>),
540    /// DateTime value.
541    DateTime(Option<DateTime>),
542    /// A small DateTime value.
543    SmallDateTime(Option<SmallDateTime>),
544    #[cfg(feature = "tds73")]
545    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
546    /// Time value.
547    Time(Option<Time>),
548    #[cfg(feature = "tds73")]
549    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
550    /// Date value.
551    Date(Option<Date>),
552    #[cfg(feature = "tds73")]
553    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
554    /// DateTime2 value.
555    DateTime2(Option<DateTime2>),
556    #[cfg(feature = "tds73")]
557    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
558    /// DateTime2 value with an offset.
559    DateTimeOffset(Option<DateTimeOffset>),
560}
561
562impl<'a> ColumnData<'a> {
563    pub(crate) fn type_name(&self) -> Cow<'static, str> {
564        match self {
565            ColumnData::U8(_) => "tinyint".into(),
566            ColumnData::I16(_) => "smallint".into(),
567            ColumnData::I32(_) => "int".into(),
568            ColumnData::I64(_) => "bigint".into(),
569            ColumnData::F32(_) => "float(24)".into(),
570            ColumnData::F64(_) => "float(53)".into(),
571            ColumnData::Bit(_) => "bit".into(),
572            ColumnData::String(None) => "nvarchar(4000)".into(),
573            ColumnData::String(Some(ref s)) if fits_nvarchar_4000(s) => "nvarchar(4000)".into(),
574            // The encoder rejects a string nvarchar(max) cannot hold.
575            ColumnData::String(Some(_)) => "nvarchar(max)".into(),
576            ColumnData::Guid(_) => "uniqueidentifier".into(),
577            // A NULL is declared as a short value of its type, and as the
578            // TYPE_INFO `encode` sends with it, so they share a cached plan.
579            ColumnData::Binary(None) => "varbinary(8000)".into(),
580            ColumnData::Binary(Some(ref b)) if b.len() <= 8000 => "varbinary(8000)".into(),
581            ColumnData::Binary(_) => "varbinary(max)".into(),
582            // Precision from the value would give each digit count its own
583            // cached plan; 38 holds every value. The scale still comes from
584            // the value, as in SqlClient, so each scale gets its own plan; a
585            // NULL's is 0, SqlClient's default.
586            ColumnData::Numeric(n) => format!(
587                "numeric({},{})",
588                NUMERIC_PARAM_PRECISION,
589                n.map_or(0, Numeric::scale)
590            )
591            .into(),
592            ColumnData::Xml(_) => "xml".into(),
593            ColumnData::DateTime(_) => "datetime".into(),
594            ColumnData::SmallDateTime(_) => "smalldatetime".into(),
595            #[cfg(feature = "tds73")]
596            ColumnData::Time(_) => "time".into(),
597            #[cfg(feature = "tds73")]
598            ColumnData::Date(_) => "date".into(),
599            #[cfg(feature = "tds73")]
600            ColumnData::DateTime2(_) => "datetime2".into(),
601            #[cfg(feature = "tds73")]
602            ColumnData::DateTimeOffset(_) => "datetimeoffset".into(),
603        }
604    }
605
606    pub(crate) async fn decode<R>(src: &mut R, ctx: &TypeInfo) -> crate::Result<ColumnData<'a>>
607    where
608        R: SqlReadBytes + Unpin,
609    {
610        let res = match ctx {
611            TypeInfo::FixedLen(fixed_ty) => fixed_len::decode(src, fixed_ty).await?,
612            TypeInfo::VarLenSized(cx) => var_len::decode(src, cx).await?,
613            TypeInfo::VarLenSizedPrecision { ty, scale, .. } => match ty {
614                VarLenType::Decimaln | VarLenType::Numericn => {
615                    ColumnData::Numeric(Numeric::decode(src, *scale).await?)
616                }
617                // Unreachable: `TypeInfo::decode` builds this variant for
618                // decimal/numeric only.
619                ty => {
620                    return Err(crate::Error::Protocol(
621                        format!("{ty:?} with precision and scale").into(),
622                    ))
623                }
624            },
625            TypeInfo::Xml { schema, size } => xml::decode(src, *size, schema.clone()).await?,
626            TypeInfo::Udt(_) => udt::decode(src).await?,
627        };
628
629        Ok(res)
630    }
631}
632
633impl<'a> ColumnData<'a> {
634    /// An RPC parameter's TYPE_INFO and value.
635    ///
636    /// The server returns an OUT parameter in the type it was sent as, so an
637    /// OUT string or binary is sent as (max): its own length would truncate
638    /// the result. SqlClient sends the larger of `Size` and the value's
639    /// length, or (max) for `Size = -1`, and rejects a NULL variable-length
640    /// OUT parameter with no `Size`; there is no `Size` here.
641    pub(crate) fn encode_rpc_param(self, dst: &mut BytesMut, out: bool) -> crate::Result<()> {
642        match self {
643            ColumnData::String(s) if out => put_nvarchar_max(dst, s.as_deref())?,
644            ColumnData::Binary(b) if out => put_varbinary_max(dst, b.as_deref())?,
645            value => return value.encode(&mut BytesMutWithTypeInfo::new(dst)),
646        }
647
648        Ok(())
649    }
650}
651
652impl<'a> Encode<BytesMutWithTypeInfo<'a>> for ColumnData<'a> {
653    fn encode(self, dst: &mut BytesMutWithTypeInfo<'a>) -> crate::Result<()> {
654        if let Some(ti) = dst.type_info() {
655            if let Some(res) = encode_number(&self, ti, dst) {
656                return res;
657            }
658            if let Some(res) = encode_legacy_datetime(&self, ti, dst) {
659                return res;
660            }
661        }
662
663        #[cfg(feature = "tds73")]
664        let data = datetime2_for_column(self, dst.type_info());
665        #[cfg(not(feature = "tds73"))]
666        let data = self;
667
668        match (data, dst.type_info()) {
669            (ColumnData::Bit(opt), Some(TypeInfo::VarLenSized(vlc)))
670                if vlc.r#type() == VarLenType::Bitn =>
671            {
672                if let Some(val) = opt {
673                    dst.put_u8(1);
674                    dst.put_u8(val as u8);
675                } else {
676                    dst.put_u8(0);
677                }
678            }
679            (ColumnData::Bit(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Bit))) => {
680                dst.put_u8(val as u8);
681            }
682            (ColumnData::Bit(opt), None) => {
683                // if TypeInfo was not given, encode a TypeInfo
684                // the first 1 is part of TYPE_INFO
685                let header = [VarLenType::Bitn as u8, 1];
686                dst.extend_from_slice(&header);
687                if let Some(val) = opt {
688                    // the second 1 is part of TYPE_VARBYTE
689                    dst.put_u8(1);
690                    dst.put_u8(val as u8);
691                } else {
692                    dst.put_u8(0);
693                }
694            }
695            (ColumnData::U8(opt), None) => {
696                let header = [VarLenType::Intn as u8, 1];
697                dst.extend_from_slice(&header);
698                if let Some(val) = opt {
699                    dst.put_u8(1);
700                    dst.put_u8(val);
701                } else {
702                    dst.put_u8(0);
703                }
704            }
705            (ColumnData::I16(opt), None) => {
706                let header = [VarLenType::Intn as u8, 2];
707                dst.extend_from_slice(&header);
708                if let Some(val) = opt {
709                    dst.put_u8(2);
710                    dst.put_i16_le(val);
711                } else {
712                    dst.put_u8(0);
713                }
714            }
715            (ColumnData::I32(opt), None) => {
716                let header = [VarLenType::Intn as u8, 4];
717                dst.extend_from_slice(&header);
718                if let Some(val) = opt {
719                    dst.put_u8(4);
720                    dst.put_i32_le(val);
721                } else {
722                    dst.put_u8(0);
723                }
724            }
725            (ColumnData::I64(opt), None) => {
726                let header = [VarLenType::Intn as u8, 8];
727                dst.extend_from_slice(&header);
728                if let Some(val) = opt {
729                    dst.put_u8(8);
730                    dst.put_i64_le(val);
731                } else {
732                    dst.put_u8(0);
733                }
734            }
735            (ColumnData::F32(opt), None) => {
736                let header = [VarLenType::Floatn as u8, 4];
737                dst.extend_from_slice(&header);
738                if let Some(val) = opt {
739                    dst.put_u8(4);
740                    dst.put_f32_le(val);
741                } else {
742                    dst.put_u8(0);
743                }
744            }
745            (ColumnData::F64(opt), None) => {
746                let header = [VarLenType::Floatn as u8, 8];
747                dst.extend_from_slice(&header);
748                if let Some(val) = opt {
749                    dst.put_u8(8);
750                    dst.put_f64_le(val);
751                } else {
752                    dst.put_u8(0);
753                }
754            }
755            (ColumnData::F64(opt), Some(TypeInfo::VarLenSized(vlc)))
756                if vlc.r#type() == VarLenType::Money =>
757            {
758                if let Some(val) = opt {
759                    money::encode(dst, vlc.len(), val)?;
760                } else {
761                    dst.put_u8(0);
762                }
763            }
764            (
765                ColumnData::F64(Some(val)),
766                Some(TypeInfo::FixedLen(ty @ (FixedLenType::Money | FixedLenType::Money4))),
767            ) => put_fixed_money(dst, *ty, |b, len| money::encode(b, len, val))?,
768            (
769                ColumnData::Numeric(Some(num)),
770                Some(TypeInfo::FixedLen(ty @ (FixedLenType::Money | FixedLenType::Money4))),
771            ) => put_fixed_money(dst, *ty, |b, len| money::encode_numeric(b, len, num))?,
772            (ColumnData::Guid(opt), Some(TypeInfo::VarLenSized(vlc)))
773                if vlc.r#type() == VarLenType::Guid =>
774            {
775                if let Some(uuid) = opt {
776                    dst.put_u8(16);
777
778                    let mut data = *uuid.as_bytes();
779                    super::guid::reorder_bytes(&mut data);
780                    dst.extend_from_slice(&data);
781                } else {
782                    dst.put_u8(0);
783                }
784            }
785            (ColumnData::Guid(opt), None) => {
786                let header = [VarLenType::Guid as u8, 16];
787                dst.extend_from_slice(&header);
788                if let Some(uuid) = opt {
789                    dst.put_u8(16);
790                    let mut data = *uuid.as_bytes();
791                    super::guid::reorder_bytes(&mut data);
792                    dst.extend_from_slice(&data);
793                } else {
794                    dst.put_u8(0);
795                }
796            }
797            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
798                if vlc.r#type() == VarLenType::BigChar
799                    || vlc.r#type() == VarLenType::BigVarChar =>
800            {
801                if let Some(str) = opt {
802                    let bytes = encode_in_collation(&str, vlc)?;
803
804                    if bytes.len() > column_limit(vlc) {
805                        return Err(crate::Error::BulkInput(
806                            format!(
807                                "Encoded string length {} exceed column limit {}",
808                                bytes.len(),
809                                column_limit(vlc)
810                            )
811                            .into(),
812                        ));
813                    }
814
815                    if vlc.len() < 0xffff {
816                        dst.put_u16_le(bytes.len() as u16);
817                        dst.extend_from_slice(bytes.as_slice());
818                    } else {
819                        put_plp(dst, &bytes);
820                    }
821                } else if vlc.len() < 0xffff {
822                    dst.put_u16_le(0xffff);
823                } else {
824                    dst.put_u64_le(0xffffffffffffffff)
825                }
826            }
827            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
828                if vlc.r#type() == VarLenType::NVarchar || vlc.r#type() == VarLenType::NChar =>
829            {
830                if let Some(str) = opt {
831                    if vlc.len() < 0xffff {
832                        let len_pos = dst.len();
833                        dst.put_u16_le(0u16);
834
835                        for chr in str.encode_utf16() {
836                            dst.put_u16_le(chr);
837                        }
838
839                        let length = dst.len() - len_pos - 2;
840
841                        if length > vlc.len() {
842                            return Err(crate::Error::BulkInput(
843                                format!(
844                                    "Encoded string length {} exceed column limit {}",
845                                    length,
846                                    vlc.len()
847                                )
848                                .into(),
849                            ));
850                        }
851
852                        let dst: &mut [u8] = dst.borrow_mut();
853                        let mut dst = &mut dst[len_pos..];
854                        dst.put_u16_le(length as u16);
855                    } else {
856                        // unknown size
857                        dst.put_u64_le(0xfffffffffffffffe);
858
859                        let len_pos = dst.len();
860                        dst.put_u32_le(0u32);
861
862                        for chr in str.encode_utf16() {
863                            dst.put_u16_le(chr);
864                        }
865
866                        let length = dst.len() - len_pos - 4;
867
868                        if length > MAX_PLP_SIZE {
869                            return Err(crate::Error::BulkInput(
870                                format!(
871                                    "Encoded string length {} exceed column limit {}",
872                                    length, MAX_PLP_SIZE
873                                )
874                                .into(),
875                            ));
876                        }
877
878                        if length > 0 {
879                            // no next blob
880                            dst.put_u32_le(0u32);
881                        }
882
883                        let dst: &mut [u8] = dst.borrow_mut();
884                        let mut dst = &mut dst[len_pos..];
885                        dst.put_u32_le(length as u32);
886                    }
887                } else if vlc.len() < 0xffff {
888                    dst.put_u16_le(0xffff);
889                } else {
890                    dst.put_u64_le(0xffffffffffffffff)
891                }
892            }
893            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
894                if vlc.r#type() == VarLenType::Text || vlc.r#type() == VarLenType::NText =>
895            {
896                if let Some(str) = opt {
897                    if vlc.r#type() == VarLenType::Text {
898                        let bytes = encode_in_collation(&str, vlc)?;
899                        let len = lob_len(bytes.len()).ok_or_else(|| too_long(bytes.len()))?;
900
901                        put_text_pointer(dst);
902                        dst.put_u32_le(len);
903                        dst.extend_from_slice(bytes.as_slice());
904                    } else {
905                        // Before writing what can be gigabytes.
906                        if let Some(units) = utf16_units_over(&str, NVARCHAR_MAX_UNITS) {
907                            return Err(too_long(2 * units));
908                        }
909
910                        put_text_pointer(dst);
911                        let len_pos = dst.len();
912                        dst.put_u32_le(0u32);
913                        for chr in str.encode_utf16() {
914                            dst.put_u16_le(chr);
915                        }
916
917                        let bytes = dst.len() - len_pos - 4;
918                        let len = lob_len(bytes).ok_or_else(|| too_long(bytes))?;
919                        dst[len_pos..len_pos + 4].copy_from_slice(&len.to_le_bytes());
920                    }
921                } else {
922                    // NULL: zero-length text pointer
923                    dst.put_u8(0);
924                }
925            }
926            (ColumnData::Binary(opt), Some(TypeInfo::VarLenSized(vlc)))
927                if vlc.r#type() == VarLenType::Image =>
928            {
929                if let Some(bytes) = opt {
930                    let len = lob_len(bytes.len()).ok_or_else(|| too_long(bytes.len()))?;
931                    put_text_pointer(dst);
932                    dst.put_u32_le(len);
933                    dst.extend_from_slice(&bytes);
934                } else {
935                    dst.put_u8(0);
936                }
937            }
938            (ColumnData::String(Some(ref s)), None) if fits_nvarchar_4000(s) => {
939                dst.put_u8(VarLenType::NVarchar as u8);
940                dst.put_u16_le(8000);
941                dst.extend_from_slice(&[0u8; 5][..]);
942
943                let mut length = 0u16;
944                let len_pos = dst.len();
945
946                dst.put_u16_le(length);
947
948                for chr in s.encode_utf16() {
949                    length += 1;
950                    dst.put_u16_le(chr);
951                }
952
953                let dst: &mut [u8] = dst.borrow_mut();
954                let bytes = (length * 2).to_le_bytes(); // u16, two bytes
955
956                for (i, byte) in bytes.iter().enumerate() {
957                    dst[len_pos + i] = *byte;
958                }
959            }
960            (ColumnData::String(Some(s)), None) => put_nvarchar_max(dst, Some(&s))?,
961            (ColumnData::Binary(opt), Some(TypeInfo::VarLenSized(vlc)))
962                if vlc.r#type() == VarLenType::BigBinary
963                    || vlc.r#type() == VarLenType::BigVarBin =>
964            {
965                if let Some(bytes) = opt {
966                    if bytes.len() > column_limit(vlc) {
967                        return Err(crate::Error::BulkInput(
968                            format!(
969                                "Binary length {} exceed column limit {}",
970                                bytes.len(),
971                                column_limit(vlc)
972                            )
973                            .into(),
974                        ));
975                    }
976
977                    if vlc.len() < 0xffff {
978                        dst.put_u16_le(bytes.len() as u16);
979                        dst.extend_from_slice(&bytes);
980                    } else {
981                        put_plp(dst, &bytes);
982                    }
983                } else if vlc.len() < 0xffff {
984                    dst.put_u16_le(0xffff);
985                } else {
986                    dst.put_u64_le(0xffffffffffffffff);
987                }
988            }
989            (ColumnData::Binary(Some(bytes)), None) if bytes.len() <= 8000 => {
990                dst.put_u8(VarLenType::BigVarBin as u8);
991                dst.put_u16_le(8000);
992                dst.put_u16_le(bytes.len() as u16);
993                dst.extend(bytes.into_owned());
994            }
995            (ColumnData::Binary(Some(bytes)), None) => put_varbinary_max(dst, Some(&bytes))?,
996            (ColumnData::DateTime(Some(dt)), None) => {
997                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 8, 8]);
998                dt.encode(&mut *dst)?;
999            }
1000            (ColumnData::SmallDateTime(Some(dt)), None) => {
1001                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 4, 4]);
1002                dt.encode(&mut *dst)?;
1003            }
1004            #[cfg(feature = "tds73")]
1005            (ColumnData::Date(opt), Some(TypeInfo::VarLenSized(vlc)))
1006                if vlc.r#type() == VarLenType::Daten =>
1007            {
1008                if let Some(dt) = opt {
1009                    dst.put_u8(3);
1010                    dt.encode(dst)?;
1011                } else {
1012                    dst.put_u8(0);
1013                }
1014            }
1015            #[cfg(feature = "tds73")]
1016            (ColumnData::Date(Some(date)), None) => {
1017                dst.extend_from_slice(&[VarLenType::Daten as u8, 3]);
1018                date.encode(&mut *dst)?;
1019            }
1020            #[cfg(feature = "tds73")]
1021            (ColumnData::Time(opt), Some(TypeInfo::VarLenSized(vlc)))
1022                if vlc.r#type() == VarLenType::Timen =>
1023            {
1024                if let Some(time) = opt {
1025                    let time = rescale_time(time, vlc.len() as u8)?;
1026                    dst.put_u8(time.len()?);
1027                    time.encode(dst)?;
1028                } else {
1029                    dst.put_u8(0);
1030                }
1031            }
1032            #[cfg(feature = "tds73")]
1033            (ColumnData::Time(Some(time)), None) => {
1034                dst.extend_from_slice(&[VarLenType::Timen as u8, time.scale(), time.len()?]);
1035                time.encode(&mut *dst)?;
1036            }
1037            #[cfg(feature = "tds73")]
1038            (ColumnData::DateTime2(opt), Some(TypeInfo::VarLenSized(vlc)))
1039                if vlc.r#type() == VarLenType::Datetime2 =>
1040            {
1041                if let Some(dt2) = opt {
1042                    let dt2 =
1043                        DateTime2::new(dt2.date(), rescale_time(dt2.time(), vlc.len() as u8)?);
1044                    dst.put_u8(dt2.time().len()? + 3);
1045                    dt2.encode(dst)?;
1046                } else {
1047                    dst.put_u8(0);
1048                }
1049            }
1050            #[cfg(feature = "tds73")]
1051            (ColumnData::DateTime2(Some(dt)), None) => {
1052                let len = dt.time().len()? + 3;
1053                dst.extend_from_slice(&[VarLenType::Datetime2 as u8, dt.time().scale(), len]);
1054                dt.encode(&mut *dst)?;
1055            }
1056            #[cfg(feature = "tds73")]
1057            (ColumnData::DateTimeOffset(opt), Some(TypeInfo::VarLenSized(vlc)))
1058                if vlc.r#type() == VarLenType::DatetimeOffsetn =>
1059            {
1060                if let Some(dto) = opt {
1061                    let dt2 = dto.datetime2();
1062                    let dt2 =
1063                        DateTime2::new(dt2.date(), rescale_time(dt2.time(), vlc.len() as u8)?);
1064                    let dto = DateTimeOffset::new(dt2, dto.offset());
1065                    dst.put_u8(dto.datetime2().time().len()? + 5);
1066                    dto.encode(dst)?;
1067                } else {
1068                    dst.put_u8(0);
1069                }
1070            }
1071            #[cfg(feature = "tds73")]
1072            (ColumnData::DateTimeOffset(Some(dto)), None) => {
1073                let headers = [
1074                    VarLenType::DatetimeOffsetn as u8,
1075                    dto.datetime2().time().scale(),
1076                    dto.datetime2().time().len()? + 5,
1077                ];
1078
1079                dst.extend_from_slice(&headers);
1080                dto.encode(&mut *dst)?;
1081            }
1082            (ColumnData::Xml(opt), Some(TypeInfo::Xml { .. })) => {
1083                if let Some(xml) = opt {
1084                    xml.into_owned().encode(dst)?;
1085                } else {
1086                    dst.put_u64_le(0xffffffffffffffff_u64);
1087                }
1088            }
1089            // As SqlBulkCopy takes a string for xml; the column is declared
1090            // nvarchar(max), see `MetaDataColumn::encode`.
1091            (ColumnData::String(opt), Some(TypeInfo::Xml { .. })) => match opt {
1092                Some(s) => put_plp_utf16(dst, &s)?,
1093                None => dst.put_u64_le(PLP_NULL),
1094            },
1095            (ColumnData::Xml(Some(xml)), None) => {
1096                dst.put_u8(VarLenType::Xml as u8);
1097                dst.put_u8(0);
1098                xml.into_owned().encode(&mut *dst)?;
1099            }
1100            (ColumnData::Numeric(opt), Some(TypeInfo::VarLenSized(vlc)))
1101                if vlc.r#type() == VarLenType::Money =>
1102            {
1103                if let Some(num) = opt {
1104                    money::encode_numeric(dst, vlc.len(), num)?;
1105                } else {
1106                    dst.put_u8(0);
1107                }
1108            }
1109            (
1110                ColumnData::Numeric(opt),
1111                Some(TypeInfo::VarLenSizedPrecision {
1112                    ty,
1113                    precision,
1114                    scale,
1115                    ..
1116                }),
1117            ) if ty == &VarLenType::Numericn || ty == &VarLenType::Decimaln => {
1118                if let Some(num) = opt {
1119                    rescale_numeric(num, *precision, *scale)?.encode(&mut *dst)?;
1120                } else {
1121                    dst.put_u8(0);
1122                }
1123            }
1124            (ColumnData::Numeric(Some(num)), None) => {
1125                // Matches `type_name`. 17 is the maximum length for precision
1126                // 38; the value's own length byte may be shorter.
1127                let headers = &[
1128                    VarLenType::Numericn as u8,
1129                    17,
1130                    NUMERIC_PARAM_PRECISION,
1131                    num.scale(),
1132                ];
1133
1134                dst.extend_from_slice(headers);
1135                num.encode(&mut *dst)?;
1136            }
1137            (data, Some(TypeInfo::VarLenSized(vlc))) if vlc.r#type() == VarLenType::SSVariant => {
1138                sql_variant::encode(&mut *dst, data, vlc.collation())?;
1139            }
1140            // Typed NULLs: an OUT parameter may not be an untyped NULL (8015).
1141            // Maximum precision and scale, so an OUT value loses nothing,
1142            // except numeric's scale 0, SqlClient's default for a NULL decimal.
1143            (ColumnData::String(None), None) => {
1144                dst.put_u8(VarLenType::NVarchar as u8);
1145                dst.put_u16_le(8000);
1146                dst.extend_from_slice(&[0u8; 5]);
1147                dst.put_u16_le(0xffff);
1148            }
1149            (ColumnData::Binary(None), None) => {
1150                dst.put_u8(VarLenType::BigVarBin as u8);
1151                dst.put_u16_le(8000);
1152                dst.put_u16_le(0xffff);
1153            }
1154            (ColumnData::Numeric(None), None) => {
1155                dst.extend_from_slice(&[
1156                    VarLenType::Numericn as u8,
1157                    17,
1158                    NUMERIC_PARAM_PRECISION,
1159                    0,
1160                    0,
1161                ]);
1162            }
1163            (ColumnData::Xml(None), None) => {
1164                dst.extend_from_slice(&[VarLenType::Xml as u8, 0]);
1165                dst.put_u64_le(PLP_NULL);
1166            }
1167            (ColumnData::DateTime(None), None) => {
1168                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 8, 0]);
1169            }
1170            (ColumnData::SmallDateTime(None), None) => {
1171                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 4, 0]);
1172            }
1173            #[cfg(feature = "tds73")]
1174            (ColumnData::Date(None), None) => {
1175                dst.extend_from_slice(&[VarLenType::Daten as u8, 0]);
1176            }
1177            #[cfg(feature = "tds73")]
1178            (ColumnData::Time(None), None) => {
1179                dst.extend_from_slice(&[VarLenType::Timen as u8, 7, 0]);
1180            }
1181            #[cfg(feature = "tds73")]
1182            (ColumnData::DateTime2(None), None) => {
1183                dst.extend_from_slice(&[VarLenType::Datetime2 as u8, 7, 0]);
1184            }
1185            #[cfg(feature = "tds73")]
1186            (ColumnData::DateTimeOffset(None), None) => {
1187                dst.extend_from_slice(&[VarLenType::DatetimeOffsetn as u8, 7, 0]);
1188            }
1189            (v, ref ti) => {
1190                return Err(crate::Error::BulkInput(
1191                    format!("invalid data type, expecting {:?} but found {:?}", ti, v).into(),
1192                ));
1193            }
1194        }
1195
1196        Ok(())
1197    }
1198}
1199
1200/// NOT NULL money is Moneyn without its length byte.
1201fn put_fixed_money(
1202    dst: &mut BytesMutWithTypeInfo<'_>,
1203    ty: FixedLenType,
1204    encode: impl FnOnce(&mut BytesMut, usize) -> crate::Result<()>,
1205) -> crate::Result<()> {
1206    let mut moneyn = BytesMut::new();
1207    encode(&mut moneyn, if ty == FixedLenType::Money4 { 4 } else { 8 })?;
1208    dst.extend_from_slice(&moneyn[1..]);
1209    Ok(())
1210}
1211
1212/// A text, ntext or image value of `len` bytes, over the 2^31 - 1 they hold.
1213fn too_long(len: usize) -> crate::Error {
1214    crate::Error::BulkInput(
1215        format!("Encoded length {len} exceeds the column limit {}", i32::MAX).into(),
1216    )
1217}
1218
1219/// The text pointer and timestamp SqlBulkCopy sends ahead of a text, ntext
1220/// or image value; the server ignores both.
1221fn put_text_pointer(dst: &mut BytesMutWithTypeInfo<'_>) {
1222    dst.put_u8(16);
1223    dst.extend_from_slice(&[0xff; 24]);
1224}
1225
1226/// Reads a [`ColumnData`] straight out of a row without any conversion,
1227/// borrowing from the row's owned data.
1228impl<'a> FromSql<'a> for ColumnData<'a> {
1229    fn from_sql(value: &'a ColumnData<'static>) -> crate::Result<Option<Self>> {
1230        Ok(Some(value.clone()))
1231    }
1232}
1233
1234/// Reads a [`ColumnData`] straight out of a row without any conversion, taking
1235/// ownership of the value.
1236impl<'a> FromSqlOwned for ColumnData<'a> {
1237    fn from_sql_owned(value: ColumnData<'static>) -> crate::Result<Option<Self>> {
1238        Ok(Some(value))
1239    }
1240}
1241
1242/// Binds a [`ColumnData`] directly as a query parameter, by reference.
1243impl<'a> ToSql for ColumnData<'a> {
1244    fn to_sql(&self) -> ColumnData<'_> {
1245        self.clone()
1246    }
1247}
1248
1249/// Binds a [`ColumnData`] directly as a query parameter, by value.
1250impl<'a> IntoSql<'a> for ColumnData<'a> {
1251    fn into_sql(self) -> ColumnData<'a> {
1252        self
1253    }
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use super::*;
1259    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
1260    use crate::tds::Collation;
1261    use crate::{Error, VarLenContext};
1262    use bytes::BytesMut;
1263
1264    async fn test_round_trip(ti: TypeInfo, d: ColumnData<'_>) {
1265        let mut buf = BytesMut::new();
1266        let mut buf_with_ti = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
1267
1268        d.clone()
1269            .encode(&mut buf_with_ti)
1270            .expect("encode must succeed");
1271
1272        let reader = &mut buf.into_sql_read_bytes();
1273        let nd = ColumnData::decode(reader, &ti)
1274            .await
1275            .expect("decode must succeed");
1276
1277        assert_eq!(nd, d);
1278
1279        reader
1280            .read_u8()
1281            .await
1282            .expect_err("decode must consume entire buffer");
1283    }
1284
1285    #[test]
1286    fn type_name_maps_each_variant() {
1287        assert_eq!(ColumnData::U8(Some(1)).type_name(), "tinyint");
1288        assert_eq!(ColumnData::I16(Some(1)).type_name(), "smallint");
1289        assert_eq!(ColumnData::I32(Some(1)).type_name(), "int");
1290        assert_eq!(ColumnData::I64(Some(1)).type_name(), "bigint");
1291        assert_eq!(ColumnData::F32(Some(1.0)).type_name(), "float(24)");
1292        assert_eq!(ColumnData::F64(Some(1.0)).type_name(), "float(53)");
1293        assert_eq!(ColumnData::Bit(Some(true)).type_name(), "bit");
1294        assert_eq!(ColumnData::Guid(None).type_name(), "uniqueidentifier");
1295        assert_eq!(ColumnData::Numeric(None).type_name(), "numeric(38,0)");
1296        assert_eq!(ColumnData::DateTime(None).type_name(), "datetime");
1297        assert_eq!(ColumnData::SmallDateTime(None).type_name(), "smalldatetime");
1298    }
1299
1300    #[test]
1301    fn type_name_string_length_thresholds() {
1302        // None and anything up to 4000 chars is a sized nvarchar; just past it
1303        // becomes nvarchar(max).
1304        assert_eq!(ColumnData::String(None).type_name(), "nvarchar(4000)");
1305        assert_eq!(
1306            ColumnData::String(Some("a".repeat(100).into())).type_name(),
1307            "nvarchar(4000)"
1308        );
1309        assert_eq!(
1310            ColumnData::String(Some("a".repeat(4000).into())).type_name(),
1311            "nvarchar(4000)"
1312        );
1313        assert_eq!(
1314            ColumnData::String(Some("a".repeat(4001).into())).type_name(),
1315            "nvarchar(max)"
1316        );
1317    }
1318
1319    /// Decodes what `encode_rpc_param` wrote: TYPE_INFO, then the value.
1320    async fn decode_rpc_param(buf: BytesMut) -> (TypeInfo, ColumnData<'static>) {
1321        let reader = &mut buf.into_sql_read_bytes();
1322        let ti = TypeInfo::decode(reader).await.unwrap();
1323        let data = ColumnData::decode(reader, &ti).await.unwrap();
1324        reader
1325            .read_u8()
1326            .await
1327            .expect_err("decode must consume entire buffer");
1328        (ti, data)
1329    }
1330
1331    #[tokio::test]
1332    async fn rpc_nulls_are_typed() {
1333        let nulls = vec![
1334            ColumnData::U8(None),
1335            ColumnData::I16(None),
1336            ColumnData::I32(None),
1337            ColumnData::I64(None),
1338            ColumnData::F32(None),
1339            ColumnData::F64(None),
1340            ColumnData::Bit(None),
1341            ColumnData::String(None),
1342            ColumnData::Guid(None),
1343            ColumnData::Binary(None),
1344            ColumnData::Numeric(None),
1345            ColumnData::Xml(None),
1346            ColumnData::DateTime(None),
1347            ColumnData::SmallDateTime(None),
1348            #[cfg(feature = "tds73")]
1349            ColumnData::Time(None),
1350            #[cfg(feature = "tds73")]
1351            ColumnData::Date(None),
1352            #[cfg(feature = "tds73")]
1353            ColumnData::DateTime2(None),
1354            #[cfg(feature = "tds73")]
1355            ColumnData::DateTimeOffset(None),
1356        ];
1357
1358        for null in nulls {
1359            for out in [false, true] {
1360                let mut buf = BytesMut::new();
1361                null.clone().encode_rpc_param(&mut buf, out).unwrap();
1362                assert_ne!(buf[0], FixedLenType::Null as u8, "{null:?}");
1363
1364                let (_, decoded) = decode_rpc_param(buf).await;
1365                assert_eq!(decoded, null);
1366            }
1367        }
1368    }
1369
1370    #[tokio::test]
1371    async fn out_strings_and_binaries_are_sent_as_max() {
1372        let values = [
1373            ColumnData::String(Some("a".into())),
1374            ColumnData::String(None),
1375            ColumnData::Binary(Some(vec![1u8].into())),
1376            ColumnData::Binary(None),
1377        ];
1378
1379        for value in values {
1380            for (out, max_len) in [(true, 0xffff), (false, 8000)] {
1381                let mut buf = BytesMut::new();
1382                value.clone().encode_rpc_param(&mut buf, out).unwrap();
1383
1384                let (ti, decoded) = decode_rpc_param(buf).await;
1385                match ti {
1386                    TypeInfo::VarLenSized(vlc) => assert_eq!(vlc.len(), max_len, "{value:?}"),
1387                    other => panic!("{other:?}"),
1388                }
1389                assert_eq!(decoded, value);
1390            }
1391        }
1392    }
1393
1394    #[tokio::test]
1395    async fn nulls_are_declared_like_values_of_their_type() {
1396        // A declaration of its own gave a NULL its own cached plan. The value
1397        // is short, and numeric's at scale 0, the scale SqlClient gives a NULL.
1398        let pairs = vec![
1399            (
1400                ColumnData::Binary(None),
1401                ColumnData::Binary(Some(vec![1].into())),
1402            ),
1403            (
1404                ColumnData::Numeric(None),
1405                ColumnData::Numeric(Some(Numeric::new_with_scale(5, 0))),
1406            ),
1407            (
1408                ColumnData::String(None),
1409                ColumnData::String(Some("a".into())),
1410            ),
1411            (ColumnData::U8(None), ColumnData::U8(Some(1))),
1412            (ColumnData::I16(None), ColumnData::I16(Some(1))),
1413            (ColumnData::I32(None), ColumnData::I32(Some(1))),
1414            (ColumnData::I64(None), ColumnData::I64(Some(1))),
1415            (ColumnData::F32(None), ColumnData::F32(Some(1.0))),
1416            (ColumnData::F64(None), ColumnData::F64(Some(1.0))),
1417            (ColumnData::Bit(None), ColumnData::Bit(Some(true))),
1418            (ColumnData::Guid(None), ColumnData::Guid(Some(Uuid::nil()))),
1419            (
1420                ColumnData::Xml(None),
1421                ColumnData::Xml(Some(Cow::Owned(XmlData::new("<a/>")))),
1422            ),
1423            (
1424                ColumnData::DateTime(None),
1425                ColumnData::DateTime(Some(DateTime::new(0, 0))),
1426            ),
1427            (
1428                ColumnData::SmallDateTime(None),
1429                ColumnData::SmallDateTime(Some(SmallDateTime::new(0, 0))),
1430            ),
1431            #[cfg(feature = "tds73")]
1432            (
1433                ColumnData::Time(None),
1434                ColumnData::Time(Some(Time::new(0, 7))),
1435            ),
1436            #[cfg(feature = "tds73")]
1437            (ColumnData::Date(None), ColumnData::Date(Some(Date::new(0)))),
1438            #[cfg(feature = "tds73")]
1439            (
1440                ColumnData::DateTime2(None),
1441                ColumnData::DateTime2(Some(DateTime2::new(Date::new(0), Time::new(0, 7)))),
1442            ),
1443            #[cfg(feature = "tds73")]
1444            (
1445                ColumnData::DateTimeOffset(None),
1446                ColumnData::DateTimeOffset(Some(DateTimeOffset::new(
1447                    DateTime2::new(Date::new(0), Time::new(0, 7)),
1448                    0,
1449                ))),
1450            ),
1451        ];
1452
1453        for (null, value) in pairs {
1454            assert_eq!(null.type_name(), value.type_name(), "{null:?}");
1455
1456            // And the TYPE_INFO sent with the NULL is the declared type's.
1457            let ti = |data: ColumnData<'static>| async {
1458                let mut buf = BytesMut::new();
1459                data.encode_rpc_param(&mut buf, false).unwrap();
1460                format!("{:?}", decode_rpc_param(buf).await.0)
1461            };
1462            assert_eq!(ti(null.clone()).await, ti(value).await, "{null:?}");
1463        }
1464    }
1465
1466    #[test]
1467    fn numeric_parameters_are_declared_at_precision_38() {
1468        for (value, scale) in [
1469            (15i128, 1u8),
1470            (123_455, 1),
1471            (10i128.pow(38) - 1, 38),
1472            (-7, 0),
1473        ] {
1474            let data = ColumnData::Numeric(Some(Numeric::new_with_scale(value, scale)));
1475            assert_eq!(data.type_name(), format!("numeric(38,{scale})"));
1476
1477            // TYPE_INFO: type, max length, precision, scale.
1478            let mut buf = BytesMut::new();
1479            data.encode(&mut BytesMutWithTypeInfo::new(&mut buf))
1480                .unwrap();
1481            assert_eq!(&buf[..4], &[VarLenType::Numericn as u8, 17, 38, scale]);
1482        }
1483    }
1484
1485    #[test]
1486    fn nvarchar_4000_threshold_counts_utf16_units() {
1487        // (string, fits nvarchar(4000)); € is 3 UTF-8 bytes and 1 unit, 😀 is
1488        // 4 bytes and 2 units.
1489        let cases = [
1490            ("\u{20AC}".repeat(1500), true),
1491            ("\u{20AC}".repeat(4000), true),
1492            ("\u{20AC}".repeat(4001), false),
1493            ("\u{1F600}".repeat(2000), true),
1494            ("\u{1F600}".repeat(2001), false),
1495        ];
1496
1497        for (s, fits) in cases {
1498            let data = ColumnData::String(Some(s.into()));
1499            let expected = if fits {
1500                "nvarchar(4000)"
1501            } else {
1502                "nvarchar(max)"
1503            };
1504            assert_eq!(data.type_name(), expected);
1505
1506            // The encoder must agree: TYPE_INFO max length 8000, or 0xFFFF for (max).
1507            let mut buf = BytesMut::new();
1508            data.encode(&mut BytesMutWithTypeInfo::new(&mut buf))
1509                .unwrap();
1510            assert_eq!(buf[0], VarLenType::NVarchar as u8);
1511            let max_len = u16::from_le_bytes([buf[1], buf[2]]);
1512            assert_eq!(max_len, if fits { 8000 } else { 0xFFFF }, "{expected}");
1513        }
1514    }
1515
1516    /// 3 UTF-8 bytes, 1 UTF-16 unit each: over 2^30 bytes, so once declared
1517    /// ntext(max), but within the 2^30 - 1 units nvarchar(max) holds.
1518    fn euros_over_1_gib() -> String {
1519        "\u{20AC}".repeat((1 << 30) / 3 + 1)
1520    }
1521
1522    #[test]
1523    fn strings_over_1_gib_of_utf8() {
1524        let euros = euros_over_1_gib();
1525        assert_eq!(
1526            ColumnData::String(Some(euros.into())).type_name(),
1527            "nvarchar(max)"
1528        );
1529
1530        // 2^30 units, one more than nvarchar(max) holds.
1531        let err = ColumnData::String(Some("a".repeat(1 << 30).into()))
1532            .encode_rpc_param(&mut BytesMut::new(), false)
1533            .unwrap_err();
1534        assert!(matches!(err, Error::Conversion(_)), "{err:?}");
1535    }
1536
1537    #[test]
1538    #[ignore = "writes 716 MB, about 40 s in a debug build"]
1539    fn string_over_1_gib_of_utf8_encodes() {
1540        let euros = euros_over_1_gib();
1541        let units = euros.len() / 3;
1542
1543        let mut buf = BytesMut::new();
1544        ColumnData::String(Some(euros.into()))
1545            .encode_rpc_param(&mut buf, false)
1546            .unwrap();
1547
1548        // TYPE_INFO (8 bytes), unknown length (8), chunk length, units, terminator.
1549        assert_eq!(buf[..3], [VarLenType::NVarchar as u8, 0xff, 0xff]);
1550        let chunk = u32::from_le_bytes(buf[16..20].try_into().unwrap());
1551        assert_eq!(chunk as usize, units * 2);
1552        assert_eq!(buf.len(), 20 + units * 2 + 4);
1553    }
1554
1555    #[test]
1556    fn type_name_binary_length_threshold() {
1557        assert_eq!(
1558            ColumnData::Binary(Some(vec![0u8; 8000].into())).type_name(),
1559            "varbinary(8000)"
1560        );
1561        assert_eq!(
1562            ColumnData::Binary(Some(vec![0u8; 8001].into())).type_name(),
1563            "varbinary(max)"
1564        );
1565        assert_eq!(ColumnData::Binary(None).type_name(), "varbinary(8000)");
1566    }
1567
1568    #[test]
1569    #[cfg(feature = "serde")]
1570    fn serde_json_round_trip() {
1571        let value = ColumnData::I32(Some(1234));
1572        let json = serde_json::to_string(&value).expect("serialize");
1573        let back: ColumnData<'static> = serde_json::from_str(&json).expect("deserialize");
1574        assert_eq!(value, back);
1575    }
1576
1577    #[tokio::test]
1578    async fn i32_with_varlen_int() {
1579        test_round_trip(
1580            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
1581            ColumnData::I32(Some(42)),
1582        )
1583        .await;
1584    }
1585
1586    #[tokio::test]
1587    async fn none_with_varlen_int() {
1588        test_round_trip(
1589            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
1590            ColumnData::I32(None),
1591        )
1592        .await;
1593    }
1594
1595    #[tokio::test]
1596    async fn i32_with_fixedlen_int() {
1597        test_round_trip(
1598            TypeInfo::FixedLen(FixedLenType::Int4),
1599            ColumnData::I32(Some(42)),
1600        )
1601        .await;
1602    }
1603
1604    #[tokio::test]
1605    async fn bit_with_varlen_bit() {
1606        test_round_trip(
1607            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Bitn, 1, None)),
1608            ColumnData::Bit(Some(true)),
1609        )
1610        .await;
1611    }
1612
1613    #[tokio::test]
1614    async fn none_with_varlen_bit() {
1615        test_round_trip(
1616            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Bitn, 1, None)),
1617            ColumnData::Bit(None),
1618        )
1619        .await;
1620    }
1621
1622    #[tokio::test]
1623    async fn bit_with_fixedlen_bit() {
1624        test_round_trip(
1625            TypeInfo::FixedLen(FixedLenType::Bit),
1626            ColumnData::Bit(Some(true)),
1627        )
1628        .await;
1629    }
1630
1631    #[tokio::test]
1632    async fn u8_with_varlen_int() {
1633        test_round_trip(
1634            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
1635            ColumnData::U8(Some(8u8)),
1636        )
1637        .await;
1638    }
1639
1640    #[tokio::test]
1641    async fn none_u8_with_varlen_int() {
1642        test_round_trip(
1643            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
1644            ColumnData::U8(None),
1645        )
1646        .await;
1647    }
1648
1649    #[tokio::test]
1650    async fn u8_with_fixedlen_int() {
1651        test_round_trip(
1652            TypeInfo::FixedLen(FixedLenType::Int1),
1653            ColumnData::U8(Some(8u8)),
1654        )
1655        .await;
1656    }
1657
1658    #[tokio::test]
1659    async fn i16_with_varlen_intn() {
1660        test_round_trip(
1661            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
1662            ColumnData::I16(Some(8i16)),
1663        )
1664        .await;
1665    }
1666
1667    #[tokio::test]
1668    async fn none_i16_with_varlen_intn() {
1669        test_round_trip(
1670            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
1671            ColumnData::I16(None),
1672        )
1673        .await;
1674    }
1675
1676    #[tokio::test]
1677    async fn none_with_varlen_intn() {
1678        test_round_trip(
1679            TypeInfo::FixedLen(FixedLenType::Int2),
1680            ColumnData::I16(Some(8i16)),
1681        )
1682        .await;
1683    }
1684
1685    #[tokio::test]
1686    async fn i64_with_varlen_intn() {
1687        test_round_trip(
1688            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
1689            ColumnData::I64(Some(8i64)),
1690        )
1691        .await;
1692    }
1693
1694    #[tokio::test]
1695    async fn i64_none_with_varlen_intn() {
1696        test_round_trip(
1697            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
1698            ColumnData::I64(None),
1699        )
1700        .await;
1701    }
1702
1703    #[tokio::test]
1704    async fn i64_with_fixedlen_int8() {
1705        test_round_trip(
1706            TypeInfo::FixedLen(FixedLenType::Int8),
1707            ColumnData::I64(Some(8i64)),
1708        )
1709        .await;
1710    }
1711
1712    #[tokio::test]
1713    async fn f32_with_varlen_floatn() {
1714        test_round_trip(
1715            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1716            ColumnData::F32(Some(8f32)),
1717        )
1718        .await;
1719    }
1720
1721    #[tokio::test]
1722    async fn null_f32_with_varlen_floatn() {
1723        test_round_trip(
1724            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1725            ColumnData::F32(None),
1726        )
1727        .await;
1728    }
1729
1730    #[tokio::test]
1731    async fn f32_with_fixedlen_float4() {
1732        test_round_trip(
1733            TypeInfo::FixedLen(FixedLenType::Float4),
1734            ColumnData::F32(Some(8f32)),
1735        )
1736        .await;
1737    }
1738
1739    #[tokio::test]
1740    async fn f64_with_varlen_floatn() {
1741        test_round_trip(
1742            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
1743            ColumnData::F64(Some(8f64)),
1744        )
1745        .await;
1746    }
1747
1748    #[tokio::test]
1749    async fn none_f64_with_varlen_floatn() {
1750        test_round_trip(
1751            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
1752            ColumnData::F64(None),
1753        )
1754        .await;
1755    }
1756
1757    #[tokio::test]
1758    async fn f64_with_fixedlen_float8() {
1759        test_round_trip(
1760            TypeInfo::FixedLen(FixedLenType::Float8),
1761            ColumnData::F64(Some(8f64)),
1762        )
1763        .await;
1764    }
1765
1766    #[tokio::test]
1767    async fn guid_with_varlen_guid() {
1768        test_round_trip(
1769            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
1770            ColumnData::Guid(Some(Uuid::new_v4())),
1771        )
1772        .await;
1773    }
1774
1775    #[tokio::test]
1776    async fn none_guid_with_varlen_guid() {
1777        test_round_trip(
1778            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
1779            ColumnData::Guid(None),
1780        )
1781        .await;
1782    }
1783
1784    #[tokio::test]
1785    async fn numeric_with_varlen_sized_precision() {
1786        test_round_trip(
1787            TypeInfo::VarLenSizedPrecision {
1788                ty: VarLenType::Numericn,
1789                size: 17,
1790                precision: 18,
1791                scale: 0,
1792            },
1793            ColumnData::Numeric(Some(Numeric::new_with_scale(23, 0))),
1794        )
1795        .await;
1796    }
1797
1798    #[tokio::test]
1799    async fn none_numeric_with_varlen_sized_precision() {
1800        test_round_trip(
1801            TypeInfo::VarLenSizedPrecision {
1802                ty: VarLenType::Numericn,
1803                size: 17,
1804                precision: 18,
1805                scale: 0,
1806            },
1807            ColumnData::Numeric(None),
1808        )
1809        .await;
1810    }
1811
1812    #[tokio::test]
1813    async fn string_with_varlen_bigchar() {
1814        test_round_trip(
1815            TypeInfo::VarLenSized(VarLenContext::new(
1816                VarLenType::BigChar,
1817                40,
1818                Some(Collation::new(13632521, 52)),
1819            )),
1820            ColumnData::String(Some("aaa".into())),
1821        )
1822        .await;
1823    }
1824
1825    #[tokio::test]
1826    async fn long_string_with_varlen_bigchar() {
1827        test_round_trip(
1828            TypeInfo::VarLenSized(VarLenContext::new(
1829                VarLenType::BigChar,
1830                0x8ffff,
1831                Some(Collation::new(13632521, 52)),
1832            )),
1833            ColumnData::String(Some("aaa".into())),
1834        )
1835        .await;
1836    }
1837
1838    #[tokio::test]
1839    async fn none_long_string_with_varlen_bigchar() {
1840        test_round_trip(
1841            TypeInfo::VarLenSized(VarLenContext::new(
1842                VarLenType::BigChar,
1843                0x8ffff,
1844                Some(Collation::new(13632521, 52)),
1845            )),
1846            ColumnData::String(None),
1847        )
1848        .await;
1849    }
1850
1851    #[tokio::test]
1852    async fn none_string_with_varlen_bigchar() {
1853        test_round_trip(
1854            TypeInfo::VarLenSized(VarLenContext::new(
1855                VarLenType::BigChar,
1856                40,
1857                Some(Collation::new(13632521, 52)),
1858            )),
1859            ColumnData::String(None),
1860        )
1861        .await;
1862    }
1863
1864    #[tokio::test]
1865    async fn string_with_varlen_bigvarchar() {
1866        test_round_trip(
1867            TypeInfo::VarLenSized(VarLenContext::new(
1868                VarLenType::BigVarChar,
1869                40,
1870                Some(Collation::new(13632521, 52)),
1871            )),
1872            ColumnData::String(Some("aaa".into())),
1873        )
1874        .await;
1875    }
1876
1877    #[tokio::test]
1878    async fn none_string_with_varlen_bigvarchar() {
1879        test_round_trip(
1880            TypeInfo::VarLenSized(VarLenContext::new(
1881                VarLenType::BigVarChar,
1882                40,
1883                Some(Collation::new(13632521, 52)),
1884            )),
1885            ColumnData::String(None),
1886        )
1887        .await;
1888    }
1889
1890    #[tokio::test]
1891    async fn empty_string_with_varlen_bigvarchar() {
1892        test_round_trip(
1893            TypeInfo::VarLenSized(VarLenContext::new(
1894                VarLenType::BigVarChar,
1895                0x8ffff,
1896                Some(Collation::new(13632521, 52)),
1897            )),
1898            ColumnData::String(Some("".into())),
1899        )
1900        .await;
1901    }
1902
1903    #[tokio::test]
1904    async fn string_with_varlen_nvarchar() {
1905        test_round_trip(
1906            TypeInfo::VarLenSized(VarLenContext::new(
1907                VarLenType::NVarchar,
1908                40,
1909                Some(Collation::new(13632521, 52)),
1910            )),
1911            ColumnData::String(Some("hhh".into())),
1912        )
1913        .await;
1914    }
1915
1916    #[tokio::test]
1917    async fn none_string_with_varlen_nvarchar() {
1918        test_round_trip(
1919            TypeInfo::VarLenSized(VarLenContext::new(
1920                VarLenType::NVarchar,
1921                40,
1922                Some(Collation::new(13632521, 52)),
1923            )),
1924            ColumnData::String(None),
1925        )
1926        .await;
1927    }
1928
1929    #[tokio::test]
1930    async fn empty_string_with_varlen_nvarchar() {
1931        test_round_trip(
1932            TypeInfo::VarLenSized(VarLenContext::new(
1933                VarLenType::NVarchar,
1934                0x8ffff,
1935                Some(Collation::new(13632521, 52)),
1936            )),
1937            ColumnData::String(Some("".into())),
1938        )
1939        .await;
1940    }
1941
1942    #[tokio::test]
1943    async fn string_with_varlen_nchar() {
1944        test_round_trip(
1945            TypeInfo::VarLenSized(VarLenContext::new(
1946                VarLenType::NChar,
1947                40,
1948                Some(Collation::new(13632521, 52)),
1949            )),
1950            ColumnData::String(Some("hhh".into())),
1951        )
1952        .await;
1953    }
1954
1955    #[tokio::test]
1956    async fn long_string_with_varlen_nchar() {
1957        test_round_trip(
1958            TypeInfo::VarLenSized(VarLenContext::new(
1959                VarLenType::NChar,
1960                0x8ffff,
1961                Some(Collation::new(13632521, 52)),
1962            )),
1963            ColumnData::String(Some("hhh".into())),
1964        )
1965        .await;
1966    }
1967
1968    #[tokio::test]
1969    async fn none_long_string_with_varlen_nchar() {
1970        test_round_trip(
1971            TypeInfo::VarLenSized(VarLenContext::new(
1972                VarLenType::NChar,
1973                0x8ffff,
1974                Some(Collation::new(13632521, 52)),
1975            )),
1976            ColumnData::String(None),
1977        )
1978        .await;
1979    }
1980
1981    #[tokio::test]
1982    async fn none_string_with_varlen_nchar() {
1983        test_round_trip(
1984            TypeInfo::VarLenSized(VarLenContext::new(
1985                VarLenType::NChar,
1986                40,
1987                Some(Collation::new(13632521, 52)),
1988            )),
1989            ColumnData::String(None),
1990        )
1991        .await;
1992    }
1993
1994    #[tokio::test]
1995    async fn binary_with_varlen_bigbinary() {
1996        test_round_trip(
1997            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 40, None)),
1998            ColumnData::Binary(Some(b"aaa".as_slice().into())),
1999        )
2000        .await;
2001    }
2002
2003    #[tokio::test]
2004    async fn long_binary_with_varlen_bigbinary() {
2005        test_round_trip(
2006            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 0x8ffff, None)),
2007            ColumnData::Binary(Some(b"aaa".as_slice().into())),
2008        )
2009        .await;
2010    }
2011
2012    #[tokio::test]
2013    async fn none_binary_with_varlen_bigbinary() {
2014        test_round_trip(
2015            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 40, None)),
2016            ColumnData::Binary(None),
2017        )
2018        .await;
2019    }
2020
2021    #[tokio::test]
2022    async fn none_long_binary_with_varlen_bigbinary() {
2023        test_round_trip(
2024            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 0x8ffff, None)),
2025            ColumnData::Binary(None),
2026        )
2027        .await;
2028    }
2029
2030    #[tokio::test]
2031    async fn binary_with_varlen_bigvarbin() {
2032        test_round_trip(
2033            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 40, None)),
2034            ColumnData::Binary(Some(b"aaa".as_slice().into())),
2035        )
2036        .await;
2037    }
2038
2039    #[tokio::test]
2040    async fn none_binary_with_varlen_bigvarbin() {
2041        test_round_trip(
2042            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 40, None)),
2043            ColumnData::Binary(None),
2044        )
2045        .await;
2046    }
2047
2048    #[tokio::test]
2049    async fn empty_binary_with_varlen_bigvarbin() {
2050        test_round_trip(
2051            TypeInfo::VarLenSized(VarLenContext::new(
2052                VarLenType::BigVarBin,
2053                0x8ffff,
2054                Some(Collation::new(13632521, 52)),
2055            )),
2056            ColumnData::Binary(Some(b"".as_slice().into())),
2057        )
2058        .await;
2059    }
2060
2061    #[tokio::test]
2062    async fn datetime_with_varlen_datetimen() {
2063        test_round_trip(
2064            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None)),
2065            ColumnData::DateTime(Some(DateTime::new(200, 3000))),
2066        )
2067        .await;
2068    }
2069
2070    // this is inconsistent: decode will decode any None datetime to smalldatetime, ignoring size
2071    // but it's non-critical, so let it be here
2072    #[tokio::test]
2073    async fn none_datetime_with_varlen_datetimen() {
2074        test_round_trip(
2075            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None)),
2076            ColumnData::DateTime(None),
2077        )
2078        .await;
2079    }
2080
2081    #[tokio::test]
2082    async fn datetime_with_fixedlen_datetime() {
2083        test_round_trip(
2084            TypeInfo::FixedLen(FixedLenType::Datetime),
2085            ColumnData::DateTime(Some(DateTime::new(200, 3000))),
2086        )
2087        .await;
2088    }
2089
2090    #[tokio::test]
2091    async fn smalldatetime_with_varlen_datetimen() {
2092        test_round_trip(
2093            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 4, None)),
2094            ColumnData::SmallDateTime(Some(SmallDateTime::new(200, 1439))),
2095        )
2096        .await;
2097    }
2098
2099    #[tokio::test]
2100    async fn none_smalldatetime_with_varlen_datetimen() {
2101        test_round_trip(
2102            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 4, None)),
2103            ColumnData::SmallDateTime(None),
2104        )
2105        .await;
2106    }
2107
2108    #[tokio::test]
2109    async fn smalldatetime_with_fixedlen_datetime4() {
2110        test_round_trip(
2111            TypeInfo::FixedLen(FixedLenType::Datetime4),
2112            ColumnData::SmallDateTime(Some(SmallDateTime::new(200, 1439))),
2113        )
2114        .await;
2115    }
2116
2117    #[cfg(feature = "tds73")]
2118    #[tokio::test]
2119    async fn date_with_varlen_daten() {
2120        test_round_trip(
2121            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Daten, 3, None)),
2122            ColumnData::Date(Some(Date::new(200))),
2123        )
2124        .await;
2125    }
2126
2127    #[cfg(feature = "tds73")]
2128    #[tokio::test]
2129    async fn none_date_with_varlen_daten() {
2130        test_round_trip(
2131            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Daten, 3, None)),
2132            ColumnData::Date(None),
2133        )
2134        .await;
2135    }
2136
2137    #[cfg(feature = "tds73")]
2138    #[tokio::test]
2139    async fn time_with_varlen_timen() {
2140        test_round_trip(
2141            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 7, None)),
2142            ColumnData::Time(Some(Time::new(55, 7))),
2143        )
2144        .await;
2145    }
2146
2147    #[cfg(feature = "tds73")]
2148    #[tokio::test]
2149    async fn none_time_with_varlen_timen() {
2150        test_round_trip(
2151            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 7, None)),
2152            ColumnData::Time(None),
2153        )
2154        .await;
2155    }
2156
2157    #[cfg(feature = "tds73")]
2158    #[tokio::test]
2159    async fn datetime2_with_varlen_datetime2() {
2160        test_round_trip(
2161            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetime2, 7, None)),
2162            ColumnData::DateTime2(Some(DateTime2::new(Date::new(55), Time::new(222, 7)))),
2163        )
2164        .await;
2165    }
2166
2167    #[cfg(feature = "tds73")]
2168    #[tokio::test]
2169    async fn none_datetime2_with_varlen_datetime2() {
2170        test_round_trip(
2171            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetime2, 7, None)),
2172            ColumnData::DateTime2(None),
2173        )
2174        .await;
2175    }
2176
2177    #[cfg(feature = "tds73")]
2178    #[tokio::test]
2179    async fn datetimeoffset_with_varlen_datetimeoffsetn() {
2180        test_round_trip(
2181            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 7, None)),
2182            ColumnData::DateTimeOffset(Some(DateTimeOffset::new(
2183                DateTime2::new(Date::new(55), Time::new(222, 7)),
2184                -8,
2185            ))),
2186        )
2187        .await;
2188    }
2189
2190    #[cfg(feature = "tds73")]
2191    #[tokio::test]
2192    async fn none_datetimeoffset_with_varlen_datetimeoffsetn() {
2193        test_round_trip(
2194            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 7, None)),
2195            ColumnData::DateTimeOffset(None),
2196        )
2197        .await;
2198    }
2199
2200    #[cfg(feature = "tds73")]
2201    #[tokio::test]
2202    async fn xml_with_xml() {
2203        test_round_trip(
2204            TypeInfo::Xml {
2205                schema: None,
2206                size: 0xfffffffffffffffe_usize,
2207            },
2208            ColumnData::Xml(Some(Cow::Owned(XmlData::new("<a>ddd</a>")))),
2209        )
2210        .await;
2211    }
2212
2213    #[cfg(feature = "tds73")]
2214    #[tokio::test]
2215    async fn none_xml_with_xml() {
2216        test_round_trip(
2217            TypeInfo::Xml {
2218                schema: None,
2219                size: 0xfffffffffffffffe_usize,
2220            },
2221            ColumnData::Xml(None),
2222        )
2223        .await;
2224    }
2225
2226    #[tokio::test]
2227    async fn invalid_type_fails() {
2228        let data = vec![
2229            (
2230                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
2231                ColumnData::I32(Some(42)),
2232            ),
2233            (
2234                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
2235                ColumnData::I32(None),
2236            ),
2237            (
2238                TypeInfo::FixedLen(FixedLenType::Int4),
2239                ColumnData::I32(None),
2240            ),
2241        ];
2242
2243        for (ti, d) in data {
2244            let mut buf = BytesMut::new();
2245            let mut buf_ti = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
2246
2247            let err = d.encode(&mut buf_ti).expect_err("encode should fail");
2248
2249            if let Error::BulkInput(_) = err {
2250            } else {
2251                panic!("Expected: Error::BulkInput, got: {:?}", err);
2252            }
2253        }
2254    }
2255
2256    #[test]
2257    fn column_data_from_sql_clones_by_reference() {
2258        let value: ColumnData<'static> = ColumnData::I32(Some(42));
2259        let out = ColumnData::from_sql(&value).unwrap();
2260        assert_eq!(out, Some(ColumnData::I32(Some(42))));
2261    }
2262
2263    #[test]
2264    fn column_data_from_sql_owned_passes_through() {
2265        let value: ColumnData<'static> = ColumnData::String(Some(Cow::Borrowed("hello")));
2266        let out = ColumnData::from_sql_owned(value).unwrap();
2267        assert_eq!(out, Some(ColumnData::String(Some(Cow::Borrowed("hello")))));
2268    }
2269
2270    #[test]
2271    fn column_data_to_sql_clones_by_reference() {
2272        let value = ColumnData::Bit(Some(true));
2273        assert_eq!(value.to_sql(), ColumnData::Bit(Some(true)));
2274        // Original is untouched.
2275        assert_eq!(value, ColumnData::Bit(Some(true)));
2276    }
2277
2278    #[test]
2279    fn column_data_into_sql_passes_through() {
2280        let value = ColumnData::F64(Some(1.5));
2281        assert_eq!(value.into_sql(), ColumnData::F64(Some(1.5)));
2282    }
2283
2284    // ----- helpers for the coverage tests below -----
2285
2286    fn encode_with_ti(ti: &TypeInfo, d: ColumnData<'_>) -> crate::Result<BytesMut> {
2287        let mut buf = BytesMut::new();
2288        {
2289            let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(ti);
2290            d.encode(&mut b)?;
2291        }
2292        Ok(buf)
2293    }
2294
2295    fn encode_without_ti(d: ColumnData<'_>) -> crate::Result<BytesMut> {
2296        let mut buf = BytesMut::new();
2297        {
2298            let mut b = BytesMutWithTypeInfo::new(&mut buf);
2299            d.encode(&mut b)?;
2300        }
2301        Ok(buf)
2302    }
2303
2304    fn expect_bulk_input(ti: TypeInfo, d: ColumnData<'_>) {
2305        let mut buf = BytesMut::new();
2306        let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
2307        let err = d.encode(&mut b).expect_err("encode should fail");
2308        assert!(matches!(err, Error::BulkInput(_)), "got {:?}", err);
2309    }
2310
2311    #[tokio::test]
2312    async fn decode_varlen_sized_precision_non_decimal_is_an_error() {
2313        let ti = TypeInfo::VarLenSizedPrecision {
2314            ty: VarLenType::Money,
2315            size: 8,
2316            precision: 0,
2317            scale: 0,
2318        };
2319        let buf = BytesMut::new();
2320        let reader = &mut buf.into_sql_read_bytes();
2321        let res = ColumnData::decode(reader, &ti).await;
2322        assert!(matches!(res, Err(Error::Protocol(_))), "{res:?}");
2323    }
2324
2325    // ----- decode: line 202 (Udt) -----
2326
2327    #[tokio::test]
2328    async fn decode_udt_type_info() {
2329        use bytes::BufMut;
2330
2331        let ti = TypeInfo::Udt(crate::tds::codec::type_info::UdtInfo {
2332            max_byte_size: 0xffff,
2333            db_name: "db".into(),
2334            schema_name: "dbo".into(),
2335            type_name: "geometry".into(),
2336            assembly_qualified_name: String::new(),
2337        });
2338
2339        let mut buf = BytesMut::new();
2340        // PLP unknown-length sentinel + one chunk + terminator.
2341        buf.put_u64_le(0xfffffffffffffffe);
2342        buf.put_u32_le(4);
2343        buf.extend_from_slice(&[1, 2, 3, 4]);
2344        buf.put_u32_le(0);
2345
2346        let reader = &mut buf.into_sql_read_bytes();
2347        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2348        assert_eq!(nd, ColumnData::Binary(Some(vec![1, 2, 3, 4].into())));
2349    }
2350
2351    // ----- F64 with Money (lines 376-383) -----
2352
2353    #[tokio::test]
2354    async fn f64_with_varlen_money() {
2355        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2356        let buf = encode_with_ti(&ti, ColumnData::F64(Some(3.5))).unwrap();
2357
2358        // Money decodes exactly, to Numeric.
2359        let nd = ColumnData::decode(&mut buf.into_sql_read_bytes(), &ti)
2360            .await
2361            .unwrap();
2362        assert_eq!(
2363            nd,
2364            ColumnData::Numeric(Some(Numeric::new_with_scale(35000, 4)))
2365        );
2366    }
2367
2368    #[tokio::test]
2369    async fn none_f64_with_varlen_money() {
2370        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2371        let buf = encode_with_ti(&ti, ColumnData::F64(None)).unwrap();
2372
2373        let nd = ColumnData::decode(&mut buf.into_sql_read_bytes(), &ti)
2374            .await
2375            .unwrap();
2376        assert_eq!(nd, ColumnData::Numeric(None));
2377    }
2378
2379    // ----- BigChar error paths (lines 426, 430-437) -----
2380
2381    #[tokio::test]
2382    async fn bigchar_unrepresentable_character_errors() {
2383        let ti = TypeInfo::VarLenSized(VarLenContext::new(
2384            VarLenType::BigChar,
2385            40,
2386            Some(Collation::new(13632521, 52)),
2387        ));
2388        let mut buf = BytesMut::new();
2389        let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
2390        // An emoji has no representation in the (single-byte) column collation.
2391        let err = ColumnData::String(Some("\u{1F600}".into()))
2392            .encode(&mut b)
2393            .expect_err("encode should fail");
2394        assert!(matches!(err, Error::Encoding(_)), "got {:?}", err);
2395    }
2396
2397    #[tokio::test]
2398    async fn bigchar_too_long_errors() {
2399        expect_bulk_input(
2400            TypeInfo::VarLenSized(VarLenContext::new(
2401                VarLenType::BigChar,
2402                2,
2403                Some(Collation::new(13632521, 52)),
2404            )),
2405            ColumnData::String(Some("aaa".into())),
2406        );
2407    }
2408
2409    // ----- NVarchar error paths (lines 481-488 small, 513-520 unknown-size) -----
2410
2411    #[tokio::test]
2412    async fn nvarchar_too_long_small_errors() {
2413        expect_bulk_input(
2414            TypeInfo::VarLenSized(VarLenContext::new(
2415                VarLenType::NVarchar,
2416                2,
2417                Some(Collation::new(13632521, 52)),
2418            )),
2419            ColumnData::String(Some("aaa".into())),
2420        );
2421    }
2422
2423    // ----- Text / NText encode arms (lines 538-587) -----
2424
2425    #[tokio::test]
2426    async fn string_with_varlen_text() {
2427        test_round_trip(
2428            TypeInfo::VarLenSized(VarLenContext::new(
2429                VarLenType::Text,
2430                40,
2431                Some(Collation::new(13632521, 52)),
2432            )),
2433            ColumnData::String(Some("hello".into())),
2434        )
2435        .await;
2436    }
2437
2438    #[tokio::test]
2439    async fn none_string_with_varlen_text() {
2440        test_round_trip(
2441            TypeInfo::VarLenSized(VarLenContext::new(
2442                VarLenType::Text,
2443                40,
2444                Some(Collation::new(13632521, 52)),
2445            )),
2446            ColumnData::String(None),
2447        )
2448        .await;
2449    }
2450
2451    #[tokio::test]
2452    async fn string_with_varlen_ntext() {
2453        test_round_trip(
2454            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NText, 40, None)),
2455            ColumnData::String(Some("hi".into())),
2456        )
2457        .await;
2458    }
2459
2460    #[tokio::test]
2461    async fn none_string_with_varlen_ntext() {
2462        test_round_trip(
2463            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NText, 40, None)),
2464            ColumnData::String(None),
2465        )
2466        .await;
2467    }
2468
2469    // ----- Binary too long (lines 650-657) -----
2470
2471    #[tokio::test]
2472    async fn binary_too_long_errors() {
2473        expect_bulk_input(
2474            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 2, None)),
2475            ColumnData::Binary(Some(b"aaa".as_slice().into())),
2476        );
2477    }
2478
2479    // ----- DateTime / SmallDateTime encode without TypeInfo (714-716, 734-736) -----
2480
2481    #[tokio::test]
2482    async fn datetime_encode_without_type_info() {
2483        let buf = encode_without_ti(ColumnData::DateTime(Some(DateTime::new(200, 3000)))).unwrap();
2484        assert_eq!(buf[0], VarLenType::Datetimen as u8);
2485        assert_eq!(buf[1], 8);
2486        assert_eq!(buf[2], 8);
2487    }
2488
2489    #[tokio::test]
2490    async fn smalldatetime_encode_without_type_info() {
2491        let buf = encode_without_ti(ColumnData::SmallDateTime(Some(SmallDateTime::new(
2492            200, 600,
2493        ))))
2494        .unwrap();
2495        assert_eq!(buf[0], VarLenType::Datetimen as u8);
2496        assert_eq!(buf[1], 4);
2497        assert_eq!(buf[2], 4);
2498    }
2499
2500    // ----- DateTime2 into a `datetime` (Datetimen) column (lines 774-780) -----
2501
2502    #[cfg(feature = "tds73")]
2503    #[tokio::test]
2504    async fn datetime2_with_varlen_datetimen() {
2505        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None));
2506        // 2020-01-01 is representable by `datetime` (>= 1900-01-01).
2507        let dt2 = DateTime2::new(Date::new(737_425), Time::new(0, 7));
2508        let buf = encode_with_ti(&ti, ColumnData::DateTime2(Some(dt2))).unwrap();
2509
2510        let reader = &mut buf.into_sql_read_bytes();
2511        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2512        assert!(matches!(nd, ColumnData::DateTime(Some(_))), "got {:?}", nd);
2513    }
2514
2515    #[cfg(feature = "tds73")]
2516    #[tokio::test]
2517    async fn none_datetime2_with_varlen_datetimen() {
2518        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None));
2519        let buf = encode_with_ti(&ti, ColumnData::DateTime2(None)).unwrap();
2520
2521        let reader = &mut buf.into_sql_read_bytes();
2522        // Just needs to decode a null cleanly.
2523        ColumnData::decode(reader, &ti).await.unwrap();
2524    }
2525
2526    // ----- Numeric into a Money column (lines 840-847) -----
2527
2528    #[tokio::test]
2529    async fn numeric_with_varlen_money() {
2530        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2531        // Numeric 3.5 (value 35000, scale 4) -> money.
2532        let buf = encode_with_ti(
2533            &ti,
2534            ColumnData::Numeric(Some(Numeric::new_with_scale(35000, 4))),
2535        )
2536        .unwrap();
2537
2538        let reader = &mut buf.into_sql_read_bytes();
2539        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2540        assert_eq!(
2541            nd,
2542            ColumnData::Numeric(Some(Numeric::new_with_scale(35000, 4)))
2543        );
2544    }
2545
2546    #[tokio::test]
2547    async fn none_numeric_with_varlen_money() {
2548        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2549        let buf = encode_with_ti(&ti, ColumnData::Numeric(None)).unwrap();
2550
2551        let reader = &mut buf.into_sql_read_bytes();
2552        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2553        assert_eq!(nd, ColumnData::Numeric(None));
2554    }
2555
2556    // ----- Numeric rescale on encode (lines 859-879) -----
2557
2558    #[tokio::test]
2559    async fn numeric_scaled_up_to_column_scale() {
2560        // Column scale 2 > value scale 0: value 23 -> 2300 at scale 2.
2561        let ti = TypeInfo::VarLenSizedPrecision {
2562            ty: VarLenType::Numericn,
2563            size: 17,
2564            precision: 18,
2565            scale: 2,
2566        };
2567        let buf = encode_with_ti(
2568            &ti,
2569            ColumnData::Numeric(Some(Numeric::new_with_scale(23, 0))),
2570        )
2571        .unwrap();
2572
2573        let reader = &mut buf.into_sql_read_bytes();
2574        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2575        assert_eq!(
2576            nd,
2577            ColumnData::Numeric(Some(Numeric::new_with_scale(2300, 2)))
2578        );
2579    }
2580
2581    #[tokio::test]
2582    async fn numeric_scaled_down_to_column_scale() {
2583        // Column scale 2 < value scale 4: 1.2345 -> 1.23 (rounded half away).
2584        let ti = TypeInfo::VarLenSizedPrecision {
2585            ty: VarLenType::Numericn,
2586            size: 17,
2587            precision: 18,
2588            scale: 2,
2589        };
2590        let buf = encode_with_ti(
2591            &ti,
2592            ColumnData::Numeric(Some(Numeric::new_with_scale(12345, 4))),
2593        )
2594        .unwrap();
2595
2596        let reader = &mut buf.into_sql_read_bytes();
2597        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2598        assert_eq!(
2599            nd,
2600            ColumnData::Numeric(Some(Numeric::new_with_scale(123, 2)))
2601        );
2602    }
2603
2604    #[tokio::test]
2605    async fn numeric_the_column_cannot_hold_is_an_error() {
2606        let decimal = |precision, scale| TypeInfo::VarLenSizedPrecision {
2607            ty: VarLenType::Decimaln,
2608            size: 17,
2609            precision,
2610            scale,
2611        };
2612        let num = |v, s| ColumnData::Numeric(Some(Numeric::new_with_scale(v, s)));
2613
2614        // Rounding overflowed i128 on the first two; 999.995 rounds to
2615        // 1000.00, which decimal(5,2) cannot hold, nor 12345 or 1234.56.
2616        for (ti, data) in [
2617            (decimal(38, 2), num(i128::MAX, 38)),
2618            (decimal(38, 2), num(i128::MIN, 38)),
2619            (decimal(5, 2), num(999_995, 3)),
2620            (decimal(5, 2), num(-999_995, 3)),
2621            (decimal(5, 2), num(12_345, 0)),
2622            (decimal(5, 2), num(123_456, 2)),
2623            (decimal(38, 0), num(10i128.pow(38), 0)),
2624        ] {
2625            let err = encode_with_ti(&ti, data.clone()).unwrap_err();
2626            assert!(matches!(err, Error::Conversion(_)), "{data:?}: {err:?}");
2627        }
2628
2629        for (ti, data, expected) in [
2630            (decimal(5, 2), num(999_994, 3), (99_999, 2)),
2631            (decimal(5, 2), num(-999_994, 3), (-99_999, 2)),
2632            (decimal(5, 2), num(-999, 0), (-99_900, 2)),
2633            (
2634                decimal(38, 0),
2635                num(10i128.pow(38) - 1, 0),
2636                (10i128.pow(38) - 1, 0),
2637            ),
2638            (decimal(38, 37), num(-15, 38), (-2, 37)),
2639        ] {
2640            let buf = encode_with_ti(&ti, data).unwrap();
2641            let nd = ColumnData::decode(&mut buf.into_sql_read_bytes(), &ti)
2642                .await
2643                .unwrap();
2644            assert_eq!(nd, num(expected.0, expected.1));
2645        }
2646    }
2647
2648    // ----- SQL_VARIANT encode (lines 897-898) -----
2649
2650    #[tokio::test]
2651    async fn ssvariant_round_trip_i32() {
2652        test_round_trip(
2653            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::SSVariant, 0, None)),
2654            ColumnData::I32(Some(42)),
2655        )
2656        .await;
2657    }
2658
2659    fn int_ti(len: usize) -> TypeInfo {
2660        TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, len, None))
2661    }
2662
2663    fn float_ti(len: usize) -> TypeInfo {
2664        TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, len, None))
2665    }
2666
2667    async fn decode_with(ti: &TypeInfo, buf: BytesMut) -> ColumnData<'static> {
2668        ColumnData::decode(&mut buf.into_sql_read_bytes(), ti)
2669            .await
2670            .unwrap()
2671    }
2672
2673    #[tokio::test]
2674    async fn bulk_integer_is_written_at_the_column_width() {
2675        let buf = encode_with_ti(&int_ti(8), ColumnData::I32(Some(-5))).unwrap();
2676        assert_eq!(
2677            &buf[..],
2678            &[8, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
2679        );
2680        assert_eq!(
2681            decode_with(&int_ti(8), buf).await,
2682            ColumnData::I64(Some(-5))
2683        );
2684
2685        let ti = TypeInfo::FixedLen(FixedLenType::Int4);
2686        let buf = encode_with_ti(&ti, ColumnData::I16(Some(-1))).unwrap();
2687        assert_eq!(&buf[..], &[0xff; 4]);
2688
2689        let buf = encode_with_ti(&int_ti(1), ColumnData::I64(Some(200))).unwrap();
2690        assert_eq!(
2691            decode_with(&int_ti(1), buf).await,
2692            ColumnData::U8(Some(200))
2693        );
2694
2695        let buf = encode_with_ti(&int_ti(2), ColumnData::I32(None)).unwrap();
2696        assert_eq!(&buf[..], &[0]);
2697    }
2698
2699    #[test]
2700    fn bulk_integer_out_of_the_column_range_errors() {
2701        for (len, value) in [
2702            (4, ColumnData::I64(Some(5_000_000_000))),
2703            (2, ColumnData::I32(Some(70_000))),
2704            (1, ColumnData::I32(Some(300))),
2705            (1, ColumnData::I16(Some(-1))),
2706        ] {
2707            let err = encode_with_ti(&int_ti(len), value).unwrap_err();
2708            assert!(matches!(err, Error::Conversion(_)), "got {err:?}");
2709        }
2710    }
2711
2712    #[tokio::test]
2713    async fn bulk_float_is_written_at_the_column_width() {
2714        let buf = encode_with_ti(&float_ti(8), ColumnData::F32(Some(1.5))).unwrap();
2715        assert_eq!(
2716            decode_with(&float_ti(8), buf).await,
2717            ColumnData::F64(Some(1.5))
2718        );
2719
2720        let ti = TypeInfo::FixedLen(FixedLenType::Float4);
2721        let buf = encode_with_ti(&ti, ColumnData::F64(Some(1.5))).unwrap();
2722        assert_eq!(&buf[..], &1.5f32.to_le_bytes());
2723
2724        let err = encode_with_ti(&float_ti(4), ColumnData::F64(Some(1e300))).unwrap_err();
2725        assert!(matches!(err, Error::Conversion(_)), "got {err:?}");
2726    }
2727
2728    #[test]
2729    fn bulk_null_into_not_null_column_errors() {
2730        let ti = TypeInfo::FixedLen(FixedLenType::Int4);
2731        let err = encode_with_ti(&ti, ColumnData::I32(None)).unwrap_err();
2732        assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
2733    }
2734
2735    #[tokio::test]
2736    async fn bulk_not_null_money() {
2737        let money = TypeInfo::FixedLen(FixedLenType::Money);
2738        let money4 = TypeInfo::FixedLen(FixedLenType::Money4);
2739        let exact = |v| ColumnData::Numeric(Some(Numeric::new_with_scale(v, 4)));
2740
2741        let buf = encode_with_ti(&money, ColumnData::F64(Some(-1234.5))).unwrap();
2742        assert_eq!(decode_with(&money, buf).await, exact(-12_345_000));
2743        let buf = encode_with_ti(&money4, ColumnData::F64(Some(1.25))).unwrap();
2744        assert_eq!(decode_with(&money4, buf).await, exact(12_500));
2745
2746        // Exact from a Numeric; through f64, .5806 would become .5807.
2747        let max_but_one = i64::MAX as i128 - 1;
2748        let buf = encode_with_ti(&money, exact(max_but_one)).unwrap();
2749        assert_eq!(decode_with(&money, buf).await, exact(max_but_one));
2750
2751        assert!(encode_with_ti(&money4, ColumnData::F64(Some(300_000.0))).is_err());
2752    }
2753
2754    /// `hh:mm:ss` plus 10^-7 s increments on 2024-05-06.
2755    #[cfg(feature = "tds73")]
2756    fn dt2(h: u64, m: u64, s: u64, increments: u64) -> ColumnData<'static> {
2757        let time = ((h * 60 + m) * 60 + s) * 10_000_000 + increments;
2758        ColumnData::DateTime2(Some(DateTime2::new(Date::new(739_011), Time::new(time, 7))))
2759    }
2760
2761    #[cfg(feature = "tds73")]
2762    async fn as_datetime(ti: &TypeInfo, value: ColumnData<'static>) -> ColumnData<'static> {
2763        decode_with(ti, encode_with_ti(ti, value).unwrap()).await
2764    }
2765
2766    #[cfg(feature = "tds73")]
2767    #[tokio::test]
2768    async fn bulk_datetime2_into_datetime_rounds_like_cast() {
2769        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None));
2770        let day = 739_011 - DAYS_YEAR_1_TO_1900 as i32;
2771
2772        // CAST(datetime2 AS datetime) on SQL Server 2022: .1234567 -> .123,
2773        // .9983333 -> .997, .9983334 -> next day.
2774        assert_eq!(
2775            as_datetime(&ti, dt2(13, 14, 15, 1_234_567)).await,
2776            ColumnData::DateTime(Some(DateTime::new(day, 47_655 * 300 + 37)))
2777        );
2778        assert_eq!(
2779            as_datetime(&ti, dt2(23, 59, 59, 9_983_333)).await,
2780            ColumnData::DateTime(Some(DateTime::new(day, 86_400 * 300 - 1)))
2781        );
2782        assert_eq!(
2783            as_datetime(&ti, dt2(23, 59, 59, 9_983_334)).await,
2784            ColumnData::DateTime(Some(DateTime::new(day + 1, 0)))
2785        );
2786    }
2787
2788    #[cfg(feature = "tds73")]
2789    #[tokio::test]
2790    async fn bulk_datetime_range_starts_in_1753() {
2791        let ti = TypeInfo::FixedLen(FixedLenType::Datetime);
2792        let at =
2793            |days| ColumnData::DateTime2(Some(DateTime2::new(Date::new(days), Time::new(0, 7))));
2794
2795        // 1753-01-01 is day 639_905 after 0001-01-01.
2796        assert_eq!(
2797            as_datetime(&ti, at(639_905)).await,
2798            ColumnData::DateTime(Some(DateTime::new(-53_690, 0)))
2799        );
2800        let err = encode_with_ti(&ti, at(639_904)).unwrap_err();
2801        assert!(matches!(err, Error::Conversion(_)), "got {err:?}");
2802    }
2803
2804    #[cfg(feature = "tds73")]
2805    #[tokio::test]
2806    async fn bulk_smalldatetime_rounds_like_cast() {
2807        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 4, None));
2808        let day = (739_011 - DAYS_YEAR_1_TO_1900) as u16;
2809
2810        // CAST(datetime2 AS smalldatetime): 29.999 s rounds down, 30 s up.
2811        assert_eq!(
2812            as_datetime(&ti, dt2(10, 0, 29, 9_990_000)).await,
2813            ColumnData::SmallDateTime(Some(SmallDateTime::new(day, 600)))
2814        );
2815        assert_eq!(
2816            as_datetime(&ti, dt2(10, 0, 30, 0)).await,
2817            ColumnData::SmallDateTime(Some(SmallDateTime::new(day, 601)))
2818        );
2819        assert_eq!(
2820            as_datetime(&ti, dt2(23, 59, 30, 0)).await,
2821            ColumnData::SmallDateTime(Some(SmallDateTime::new(day + 1, 0)))
2822        );
2823
2824        let ti = TypeInfo::FixedLen(FixedLenType::Datetime4);
2825        let buf = encode_with_ti(&ti, ColumnData::DateTime(Some(DateTime::new(5, 9_000)))).unwrap();
2826        assert_eq!(
2827            decode_with(&ti, buf).await,
2828            ColumnData::SmallDateTime(Some(SmallDateTime::new(5, 1)))
2829        );
2830    }
2831
2832    #[cfg(feature = "tds73")]
2833    #[tokio::test]
2834    async fn bulk_time_is_written_at_the_column_scale() {
2835        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 3, None));
2836        let buf = encode_with_ti(&ti, ColumnData::Time(Some(Time::new(1_234_567, 7)))).unwrap();
2837        assert_eq!(&buf[..], &[4, 123, 0, 0, 0]);
2838
2839        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 0, None));
2840        let dto = DateTimeOffset::new(
2841            DateTime2::new(Date::new(739_011), Time::new(15_000_000, 7)),
2842            60,
2843        );
2844        let buf = encode_with_ti(&ti, ColumnData::DateTimeOffset(Some(dto))).unwrap();
2845        // length, 3 time bytes (1 s), 3 date bytes, 2 offset bytes.
2846        assert_eq!(buf[0], 8);
2847        assert_eq!(&buf[1..4], &[1, 0, 0]);
2848
2849        assert_eq!(
2850            rescale_time(Time::new(5, 0), 7).unwrap().increments(),
2851            50_000_000
2852        );
2853        assert!(rescale_time(Time::new(5, 9), 7).is_err());
2854    }
2855
2856    #[cfg(feature = "tds73")]
2857    #[test]
2858    fn bulk_times_from_24_hours_up_are_errors() {
2859        let column = |ty, len| TypeInfo::VarLenSized(VarLenContext::new(ty, len, None));
2860        let at = |increments| DateTime2::new(Date::new(739_011), Time::new(increments, 7));
2861
2862        // u64::MAX overflowed converting to datetime's 1/300 s.
2863        for increments in [u64::MAX, Time::LEAP_SECOND, 864_000_000_000] {
2864            for (ti, data) in [
2865                (
2866                    column(VarLenType::Timen, 3),
2867                    ColumnData::Time(Some(Time::new(increments, 7))),
2868                ),
2869                (
2870                    column(VarLenType::Datetime2, 7),
2871                    ColumnData::DateTime2(Some(at(increments))),
2872                ),
2873                (
2874                    column(VarLenType::Datetimen, 8),
2875                    ColumnData::DateTime2(Some(at(increments))),
2876                ),
2877                (
2878                    column(VarLenType::Datetimen, 4),
2879                    ColumnData::DateTime2(Some(at(increments))),
2880                ),
2881            ] {
2882                let err = encode_with_ti(&ti, data).unwrap_err();
2883                assert!(matches!(err, Error::Conversion(_)), "{ti:?}: {err:?}");
2884            }
2885        }
2886    }
2887
2888    #[tokio::test]
2889    async fn bulk_max_values_over_64k() {
2890        let collation = Some(Collation::new(13632521, 52));
2891        test_round_trip(
2892            TypeInfo::VarLenSized(VarLenContext::new(
2893                VarLenType::BigVarChar,
2894                0xffff,
2895                collation,
2896            )),
2897            ColumnData::String(Some("a".repeat(70_000).into())),
2898        )
2899        .await;
2900        test_round_trip(
2901            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NVarchar, 0xffff, collation)),
2902            ColumnData::String(Some("a".repeat(40_000).into())),
2903        )
2904        .await;
2905        test_round_trip(
2906            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 0xffff, None)),
2907            ColumnData::Binary(Some(vec![7u8; 70_000].into())),
2908        )
2909        .await;
2910    }
2911
2912    #[tokio::test]
2913    async fn bulk_image() {
2914        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Image, 0x7fff_ffff, None));
2915        test_round_trip(ti.clone(), ColumnData::Binary(Some(vec![1u8, 2, 3].into()))).await;
2916
2917        let buf = encode_with_ti(&ti, ColumnData::Binary(None)).unwrap();
2918        assert_eq!(&buf[..], &[0]);
2919    }
2920
2921    #[test]
2922    fn bulk_varchar_uses_the_column_code_page() {
2923        // Greek_CI_AS: windows-1253.
2924        let ti = TypeInfo::VarLenSized(VarLenContext::new(
2925            VarLenType::BigVarChar,
2926            10,
2927            Some(Collation::new(0x00d0_0408, 0)),
2928        ));
2929        let buf = encode_with_ti(&ti, ColumnData::String(Some("\u{3b1}\u{3b2}".into()))).unwrap();
2930        assert_eq!(&buf[..], &[2, 0, 0xe1, 0xe2]);
2931    }
2932
2933    /// 2^30 units, one more than the 2^31 - 1 bytes of xml and ntext hold.
2934    /// Their byte counts were u32s, which overflowed from 2^31 units.
2935    #[test]
2936    fn utf16_values_over_2_gib_are_errors() {
2937        let s = "a".repeat(1 << 30);
2938        let collation = Some(Collation::new(0x00d0_0409, 52));
2939
2940        let ntext = TypeInfo::VarLenSized(VarLenContext::new(
2941            VarLenType::NText,
2942            0x7fff_fffe,
2943            collation,
2944        ));
2945        let err = encode_with_ti(&ntext, ColumnData::String(Some(s.as_str().into())))
2946            .map(drop)
2947            .unwrap_err();
2948        assert!(matches!(err, Error::BulkInput(_)), "{err:?}");
2949
2950        let xml = TypeInfo::Xml {
2951            schema: None,
2952            size: 0xfffffffffffffffe_usize,
2953        };
2954        let err = encode_with_ti(&xml, ColumnData::String(Some(s.as_str().into())))
2955            .map(drop)
2956            .unwrap_err();
2957        assert!(matches!(err, Error::Conversion(_)), "{err:?}");
2958
2959        let err = ColumnData::Xml(Some(Cow::Owned(XmlData::new(s))))
2960            .encode_rpc_param(&mut BytesMut::new(), false)
2961            .unwrap_err();
2962        assert!(matches!(err, Error::Conversion(_)), "{err:?}");
2963    }
2964
2965    /// One byte more than varbinary(max) and image hold; `as u32` wrapped
2966    /// from 4 GiB. The zeroed pages are never touched.
2967    #[test]
2968    fn binaries_over_2_gib_are_errors() {
2969        let big = vec![0u8; 1 << 31];
2970
2971        for out in [false, true] {
2972            let err = ColumnData::Binary(Some(big.as_slice().into()))
2973                .encode_rpc_param(&mut BytesMut::new(), out)
2974                .unwrap_err();
2975            assert!(matches!(err, Error::Conversion(_)), "{out}: {err:?}");
2976        }
2977
2978        let image = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Image, 0x7fff_ffff, None));
2979        let err = encode_with_ti(&image, ColumnData::Binary(Some(big.as_slice().into())))
2980            .map(drop)
2981            .unwrap_err();
2982        assert!(matches!(err, Error::BulkInput(_)), "{err:?}");
2983    }
2984
2985    #[test]
2986    #[ignore = "allocates 4 GiB"]
2987    fn text_over_2_gib_is_an_error() {
2988        let utf8 = Some(Collation::new(0x0400_0409, 0));
2989        let text = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Text, 0x7fff_ffff, utf8));
2990        let s = "a".repeat(1 << 31);
2991        let err = encode_with_ti(&text, ColumnData::String(Some(s.into())))
2992            .map(drop)
2993            .unwrap_err();
2994        assert!(matches!(err, Error::BulkInput(_)), "{err:?}");
2995    }
2996
2997    /// Rounding carried them into the next day: 24:00:00.5 was stored as
2998    /// 00:00:00.5 the day after.
2999    #[test]
3000    fn bulk_datetimes_past_midnight_are_errors() {
3001        let datetimen =
3002            |len| TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, len, None));
3003        let past = ColumnData::DateTime(Some(DateTime::new(0, 25_920_150)));
3004        let leap = ColumnData::DateTime(Some(DateTime::new(0, u32::MAX)));
3005        let small = ColumnData::SmallDateTime(Some(SmallDateTime::new(0, 1440)));
3006
3007        for (ti, data) in [
3008            (TypeInfo::FixedLen(FixedLenType::Datetime), past.clone()),
3009            (datetimen(8), leap.clone()),
3010            (TypeInfo::FixedLen(FixedLenType::Datetime4), past),
3011            (datetimen(4), leap),
3012            (TypeInfo::FixedLen(FixedLenType::Datetime), small.clone()),
3013            (datetimen(4), small),
3014        ] {
3015            let err = encode_with_ti(&ti, data).unwrap_err();
3016            assert!(matches!(err, Error::Conversion(_)), "{ti:?}: {err:?}");
3017        }
3018    }
3019}