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