Skip to main content

tiberius/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, VarLenType};
48#[cfg(feature = "tds73")]
49use crate::tds::time::{Date, DateTime2, DateTimeOffset, Time};
50use crate::{
51    tds::{time::DateTime, time::SmallDateTime, xml::XmlData, Numeric},
52    FromSql, FromSqlOwned, IntoSql, SqlReadBytes, ToSql,
53};
54use bytes::BufMut;
55pub(crate) use bytes_mut_with_type_info::BytesMutWithTypeInfo;
56use std::borrow::{BorrowMut, Cow};
57use uuid::Uuid;
58
59const MAX_NVARCHAR_SIZE: usize = 1 << 30;
60
61/// Number of days between `0001-01-01` (the `DateTime2`/`Date` epoch) and
62/// `1900-01-01` (the `datetime`/`Datetimen` epoch).
63#[cfg(feature = "tds73")]
64const DAYS_YEAR_1_TO_1900: u32 = 693_595;
65
66/// Converts a [`DateTime2`] value into the legacy `datetime` ([`DateTime`])
67/// wire representation.
68///
69/// This is used when bulk-inserting a `DateTime2`/`Date` value into a column
70/// whose server-side type is `datetime` (`Datetimen`). The `datetime` type
71/// counts days from `1900-01-01` and stores the time of day as 1/300-second
72/// fragments, so the sub-second precision of the source value is degraded to
73/// match. Returns a [`Conversion`] error if the date is earlier than
74/// `1900-01-01`, which `datetime` cannot represent.
75///
76/// [`Conversion`]: crate::Error::Conversion
77#[cfg(feature = "tds73")]
78fn datetime2_to_datetime(dt2: &DateTime2) -> crate::Result<DateTime> {
79    let dt2_days = dt2.date().days();
80
81    let days = dt2_days.checked_sub(DAYS_YEAR_1_TO_1900).ok_or_else(|| {
82        crate::Error::Conversion(
83            format!(
84                "invalid datetime, expecting a date not earlier than 1900-01-01 but got {} days after year 1",
85                dt2_days
86            )
87            .into(),
88        )
89    })? as i32;
90
91    // `increments` are counted in 10^-scale seconds; convert to nanoseconds and
92    // then to the 1/300-second fragments used by `datetime`, degrading the
93    // sub-second precision in the process.
94    let time = dt2.time();
95    let nanos = time.increments() as u128 * 10u128.pow(9 - time.scale() as u32);
96    let seconds_fragments = (nanos * 300 / 1_000_000_000) as u32;
97
98    Ok(DateTime::new(days, seconds_fragments))
99}
100
101#[derive(Clone, Debug, PartialEq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103/// A container of a value that can be represented as a TDS value.
104pub enum ColumnData<'a> {
105    /// 8-bit integer, unsigned.
106    U8(Option<u8>),
107    /// 16-bit integer, signed.
108    I16(Option<i16>),
109    /// 32-bit integer, signed.
110    I32(Option<i32>),
111    /// 64-bit integer, signed.
112    I64(Option<i64>),
113    /// 32-bit floating point number.
114    F32(Option<f32>),
115    /// 64-bit floating point number.
116    F64(Option<f64>),
117    /// Boolean.
118    Bit(Option<bool>),
119    /// A string value.
120    String(Option<Cow<'a, str>>),
121    /// A Guid (UUID) value.
122    Guid(Option<Uuid>),
123    /// Binary data.
124    Binary(Option<Cow<'a, [u8]>>),
125    /// Numeric value (a decimal).
126    Numeric(Option<Numeric>),
127    /// XML data.
128    Xml(Option<Cow<'a, XmlData>>),
129    /// DateTime value.
130    DateTime(Option<DateTime>),
131    /// A small DateTime value.
132    SmallDateTime(Option<SmallDateTime>),
133    #[cfg(feature = "tds73")]
134    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
135    /// Time value.
136    Time(Option<Time>),
137    #[cfg(feature = "tds73")]
138    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
139    /// Date value.
140    Date(Option<Date>),
141    #[cfg(feature = "tds73")]
142    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
143    /// DateTime2 value.
144    DateTime2(Option<DateTime2>),
145    #[cfg(feature = "tds73")]
146    #[cfg_attr(docsrs, doc(cfg(feature = "tds73")))]
147    /// DateTime2 value with an offset.
148    DateTimeOffset(Option<DateTimeOffset>),
149}
150
151impl<'a> ColumnData<'a> {
152    pub(crate) fn type_name(&self) -> Cow<'static, str> {
153        match self {
154            ColumnData::U8(_) => "tinyint".into(),
155            ColumnData::I16(_) => "smallint".into(),
156            ColumnData::I32(_) => "int".into(),
157            ColumnData::I64(_) => "bigint".into(),
158            ColumnData::F32(_) => "float(24)".into(),
159            ColumnData::F64(_) => "float(53)".into(),
160            ColumnData::Bit(_) => "bit".into(),
161            ColumnData::String(None) => "nvarchar(4000)".into(),
162            ColumnData::String(Some(ref s)) if s.len() <= 4000 => "nvarchar(4000)".into(),
163            ColumnData::String(Some(ref s)) if s.len() <= MAX_NVARCHAR_SIZE => {
164                "nvarchar(max)".into()
165            }
166            ColumnData::String(_) => "ntext(max)".into(),
167            ColumnData::Guid(_) => "uniqueidentifier".into(),
168            ColumnData::Binary(Some(ref b)) if b.len() <= 8000 => "varbinary(8000)".into(),
169            ColumnData::Binary(_) => "varbinary(max)".into(),
170            ColumnData::Numeric(Some(ref n)) => {
171                format!("numeric({},{})", n.precision(), n.scale()).into()
172            }
173            ColumnData::Numeric(None) => "numeric".into(),
174            ColumnData::Xml(_) => "xml".into(),
175            ColumnData::DateTime(_) => "datetime".into(),
176            ColumnData::SmallDateTime(_) => "smalldatetime".into(),
177            #[cfg(feature = "tds73")]
178            ColumnData::Time(_) => "time".into(),
179            #[cfg(feature = "tds73")]
180            ColumnData::Date(_) => "date".into(),
181            #[cfg(feature = "tds73")]
182            ColumnData::DateTime2(_) => "datetime2".into(),
183            #[cfg(feature = "tds73")]
184            ColumnData::DateTimeOffset(_) => "datetimeoffset".into(),
185        }
186    }
187
188    pub(crate) async fn decode<R>(src: &mut R, ctx: &TypeInfo) -> crate::Result<ColumnData<'a>>
189    where
190        R: SqlReadBytes + Unpin,
191    {
192        let res = match ctx {
193            TypeInfo::FixedLen(fixed_ty) => fixed_len::decode(src, fixed_ty).await?,
194            TypeInfo::VarLenSized(cx) => var_len::decode(src, cx).await?,
195            TypeInfo::VarLenSizedPrecision { ty, scale, .. } => match ty {
196                VarLenType::Decimaln | VarLenType::Numericn => {
197                    ColumnData::Numeric(Numeric::decode(src, *scale).await?)
198                }
199                _ => todo!(),
200            },
201            TypeInfo::Xml { schema, size } => xml::decode(src, *size, schema.clone()).await?,
202            TypeInfo::Udt(_) => udt::decode(src).await?,
203        };
204
205        Ok(res)
206    }
207}
208
209impl<'a> Encode<BytesMutWithTypeInfo<'a>> for ColumnData<'a> {
210    fn encode(self, dst: &mut BytesMutWithTypeInfo<'a>) -> crate::Result<()> {
211        match (self, dst.type_info()) {
212            (ColumnData::Bit(opt), Some(TypeInfo::VarLenSized(vlc)))
213                if vlc.r#type() == VarLenType::Bitn =>
214            {
215                if let Some(val) = opt {
216                    dst.put_u8(1);
217                    dst.put_u8(val as u8);
218                } else {
219                    dst.put_u8(0);
220                }
221            }
222            (ColumnData::Bit(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Bit))) => {
223                dst.put_u8(val as u8);
224            }
225            (ColumnData::Bit(opt), None) => {
226                // if TypeInfo was not given, encode a TypeInfo
227                // the first 1 is part of TYPE_INFO
228                let header = [VarLenType::Bitn as u8, 1];
229                dst.extend_from_slice(&header);
230                if let Some(val) = opt {
231                    // the second 1 is part of TYPE_VARBYTE
232                    dst.put_u8(1);
233                    dst.put_u8(val as u8);
234                } else {
235                    dst.put_u8(0);
236                }
237            }
238            (ColumnData::U8(opt), Some(TypeInfo::VarLenSized(vlc)))
239                if vlc.r#type() == VarLenType::Intn =>
240            {
241                if let Some(val) = opt {
242                    dst.put_u8(1);
243                    dst.put_u8(val);
244                } else {
245                    dst.put_u8(0);
246                }
247            }
248            (ColumnData::U8(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Int1))) => {
249                dst.put_u8(val);
250            }
251            (ColumnData::U8(opt), None) => {
252                let header = [VarLenType::Intn as u8, 1];
253                dst.extend_from_slice(&header);
254                if let Some(val) = opt {
255                    dst.put_u8(1);
256                    dst.put_u8(val);
257                } else {
258                    dst.put_u8(0);
259                }
260            }
261            (ColumnData::I16(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Int2))) => {
262                dst.put_i16_le(val);
263            }
264            (ColumnData::I16(opt), Some(TypeInfo::VarLenSized(vlc)))
265                if vlc.r#type() == VarLenType::Intn =>
266            {
267                if let Some(val) = opt {
268                    dst.put_u8(2);
269                    dst.put_i16_le(val);
270                } else {
271                    dst.put_u8(0);
272                }
273            }
274            (ColumnData::I16(opt), None) => {
275                let header = [VarLenType::Intn as u8, 2];
276                dst.extend_from_slice(&header);
277                if let Some(val) = opt {
278                    dst.put_u8(2);
279                    dst.put_i16_le(val);
280                } else {
281                    dst.put_u8(0);
282                }
283            }
284            (ColumnData::I32(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Int4))) => {
285                dst.put_i32_le(val);
286            }
287            (ColumnData::I32(opt), Some(TypeInfo::VarLenSized(vlc)))
288                if vlc.r#type() == VarLenType::Intn =>
289            {
290                if let Some(val) = opt {
291                    dst.put_u8(4);
292                    dst.put_i32_le(val);
293                } else {
294                    dst.put_u8(0);
295                }
296            }
297            (ColumnData::I32(opt), None) => {
298                let header = [VarLenType::Intn as u8, 4];
299                dst.extend_from_slice(&header);
300                if let Some(val) = opt {
301                    dst.put_u8(4);
302                    dst.put_i32_le(val);
303                } else {
304                    dst.put_u8(0);
305                }
306            }
307            (ColumnData::I64(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Int8))) => {
308                dst.put_i64_le(val);
309            }
310            (ColumnData::I64(opt), Some(TypeInfo::VarLenSized(vlc)))
311                if vlc.r#type() == VarLenType::Intn =>
312            {
313                if let Some(val) = opt {
314                    dst.put_u8(8);
315                    dst.put_i64_le(val);
316                } else {
317                    dst.put_u8(0);
318                }
319            }
320            (ColumnData::I64(opt), None) => {
321                let header = [VarLenType::Intn as u8, 8];
322                dst.extend_from_slice(&header);
323                if let Some(val) = opt {
324                    dst.put_u8(8);
325                    dst.put_i64_le(val);
326                } else {
327                    dst.put_u8(0);
328                }
329            }
330            (ColumnData::F32(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Float4))) => {
331                dst.put_f32_le(val);
332            }
333            (ColumnData::F32(opt), Some(TypeInfo::VarLenSized(vlc)))
334                if vlc.r#type() == VarLenType::Floatn =>
335            {
336                if let Some(val) = opt {
337                    dst.put_u8(4);
338                    dst.put_f32_le(val);
339                } else {
340                    dst.put_u8(0);
341                }
342            }
343            (ColumnData::F32(opt), None) => {
344                let header = [VarLenType::Floatn as u8, 4];
345                dst.extend_from_slice(&header);
346                if let Some(val) = opt {
347                    dst.put_u8(4);
348                    dst.put_f32_le(val);
349                } else {
350                    dst.put_u8(0);
351                }
352            }
353            (ColumnData::F64(Some(val)), Some(TypeInfo::FixedLen(FixedLenType::Float8))) => {
354                dst.put_f64_le(val);
355            }
356            (ColumnData::F64(opt), Some(TypeInfo::VarLenSized(vlc)))
357                if vlc.r#type() == VarLenType::Floatn =>
358            {
359                if let Some(val) = opt {
360                    dst.put_u8(8);
361                    dst.put_f64_le(val);
362                } else {
363                    dst.put_u8(0);
364                }
365            }
366            (ColumnData::F64(opt), None) => {
367                let header = [VarLenType::Floatn as u8, 8];
368                dst.extend_from_slice(&header);
369                if let Some(val) = opt {
370                    dst.put_u8(8);
371                    dst.put_f64_le(val);
372                } else {
373                    dst.put_u8(0);
374                }
375            }
376            (ColumnData::F64(opt), Some(TypeInfo::VarLenSized(vlc)))
377                if vlc.r#type() == VarLenType::Money =>
378            {
379                if let Some(val) = opt {
380                    money::encode(dst, vlc.len(), val);
381                } else {
382                    dst.put_u8(0);
383                }
384            }
385            (ColumnData::Guid(opt), Some(TypeInfo::VarLenSized(vlc)))
386                if vlc.r#type() == VarLenType::Guid =>
387            {
388                if let Some(uuid) = opt {
389                    dst.put_u8(16);
390
391                    let mut data = *uuid.as_bytes();
392                    super::guid::reorder_bytes(&mut data);
393                    dst.extend_from_slice(&data);
394                } else {
395                    dst.put_u8(0);
396                }
397            }
398            (ColumnData::Guid(opt), None) => {
399                let header = [VarLenType::Guid as u8, 16];
400                dst.extend_from_slice(&header);
401                if let Some(uuid) = opt {
402                    dst.put_u8(16);
403                    let mut data = *uuid.as_bytes();
404                    super::guid::reorder_bytes(&mut data);
405                    dst.extend_from_slice(&data);
406                } else {
407                    dst.put_u8(0);
408                }
409            }
410            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
411                if vlc.r#type() == VarLenType::BigChar
412                    || vlc.r#type() == VarLenType::BigVarChar =>
413            {
414                if let Some(str) = opt {
415                    let mut encoder = vlc.collation().as_ref().unwrap().encoding()?.new_encoder();
416                    let len = encoder
417                        .max_buffer_length_from_utf8_without_replacement(str.len())
418                        .unwrap();
419                    let mut bytes = Vec::with_capacity(len);
420                    let (res, _) = encoder.encode_from_utf8_to_vec_without_replacement(
421                        str.as_ref(),
422                        &mut bytes,
423                        true,
424                    );
425                    if let encoding_rs::EncoderResult::Unmappable(_) = res {
426                        return Err(crate::Error::Encoding("unrepresentable character".into()));
427                    }
428
429                    if bytes.len() > vlc.len() {
430                        return Err(crate::Error::BulkInput(
431                            format!(
432                                "Encoded string length {} exceed column limit {}",
433                                bytes.len(),
434                                vlc.len()
435                            )
436                            .into(),
437                        ));
438                    }
439
440                    if vlc.len() < 0xffff {
441                        dst.put_u16_le(bytes.len() as u16);
442                        dst.extend_from_slice(bytes.as_slice());
443                    } else {
444                        // unknown size
445                        dst.put_u64_le(0xfffffffffffffffe);
446
447                        assert!(
448                            str.len() < 0xffffffff,
449                            "if str longer than this, need to implement multiple blobs"
450                        );
451
452                        dst.put_u32_le(bytes.len() as u32);
453                        dst.extend_from_slice(bytes.as_slice());
454
455                        if !bytes.is_empty() {
456                            // no next blob
457                            dst.put_u32_le(0u32);
458                        }
459                    }
460                } else if vlc.len() < 0xffff {
461                    dst.put_u16_le(0xffff);
462                } else {
463                    dst.put_u64_le(0xffffffffffffffff)
464                }
465            }
466            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
467                if vlc.r#type() == VarLenType::NVarchar || vlc.r#type() == VarLenType::NChar =>
468            {
469                if let Some(str) = opt {
470                    if vlc.len() < 0xffff {
471                        let len_pos = dst.len();
472                        dst.put_u16_le(0u16);
473
474                        for chr in str.encode_utf16() {
475                            dst.put_u16_le(chr);
476                        }
477
478                        let length = dst.len() - len_pos - 2;
479
480                        if length > vlc.len() {
481                            return Err(crate::Error::BulkInput(
482                                format!(
483                                    "Encoded string length {} exceed column limit {}",
484                                    length,
485                                    vlc.len()
486                                )
487                                .into(),
488                            ));
489                        }
490
491                        let dst: &mut [u8] = dst.borrow_mut();
492                        let mut dst = &mut dst[len_pos..];
493                        dst.put_u16_le(length as u16);
494                    } else {
495                        // unknown size
496                        dst.put_u64_le(0xfffffffffffffffe);
497
498                        assert!(
499                            str.len() < 0xffffffff,
500                            "if str longer than this, need to implement multiple blobs"
501                        );
502
503                        let len_pos = dst.len();
504                        dst.put_u32_le(0u32);
505
506                        for chr in str.encode_utf16() {
507                            dst.put_u16_le(chr);
508                        }
509
510                        let length = dst.len() - len_pos - 4;
511
512                        if length > vlc.len() {
513                            return Err(crate::Error::BulkInput(
514                                format!(
515                                    "Encoded string length {} exceed column limit {}",
516                                    length,
517                                    vlc.len()
518                                )
519                                .into(),
520                            ));
521                        }
522
523                        if length > 0 {
524                            // no next blob
525                            dst.put_u32_le(0u32);
526                        }
527
528                        let dst: &mut [u8] = dst.borrow_mut();
529                        let mut dst = &mut dst[len_pos..];
530                        dst.put_u32_le(length as u32);
531                    }
532                } else if vlc.len() < 0xffff {
533                    dst.put_u16_le(0xffff);
534                } else {
535                    dst.put_u64_le(0xffffffffffffffff)
536                }
537            }
538            (ColumnData::String(opt), Some(TypeInfo::VarLenSized(vlc)))
539                if vlc.r#type() == VarLenType::Text || vlc.r#type() == VarLenType::NText =>
540            {
541                if let Some(str) = opt {
542                    // TEXT/NTEXT row values carry a text pointer and a timestamp
543                    // ahead of the payload. The server ignores the values we
544                    // supply on bulk-load, so send a fixed-size dummy pointer
545                    // and timestamp.
546                    dst.put_u8(16); // text pointer length
547                    dst.extend_from_slice(&[0u8; 16]); // text pointer
548                    dst.extend_from_slice(&[0u8; 8]); // timestamp
549
550                    if vlc.r#type() == VarLenType::Text {
551                        // single-byte character data, encoded with the column collation
552                        let mut encoder =
553                            vlc.collation().as_ref().unwrap().encoding()?.new_encoder();
554                        let len = encoder
555                            .max_buffer_length_from_utf8_without_replacement(str.len())
556                            .unwrap();
557                        let mut bytes = Vec::with_capacity(len);
558                        let (res, _) = encoder.encode_from_utf8_to_vec_without_replacement(
559                            str.as_ref(),
560                            &mut bytes,
561                            true,
562                        );
563                        if let encoding_rs::EncoderResult::Unmappable(_) = res {
564                            return Err(crate::Error::Encoding("unrepresentable character".into()));
565                        }
566
567                        dst.put_u32_le(bytes.len() as u32);
568                        dst.extend_from_slice(bytes.as_slice());
569                    } else {
570                        // NTEXT: UCS-2/UTF-16LE data
571                        let len_pos = dst.len();
572                        dst.put_u32_le(0u32);
573
574                        let mut length = 0u32;
575                        for chr in str.encode_utf16() {
576                            length += 2;
577                            dst.put_u16_le(chr);
578                        }
579
580                        let dst: &mut [u8] = dst.borrow_mut();
581                        let bytes = length.to_le_bytes();
582                        dst[len_pos..len_pos + 4].copy_from_slice(&bytes);
583                    }
584                } else {
585                    // NULL: zero-length text pointer
586                    dst.put_u8(0);
587                }
588            }
589            (ColumnData::String(Some(ref s)), None) if s.len() <= 4000 => {
590                dst.put_u8(VarLenType::NVarchar as u8);
591                dst.put_u16_le(8000);
592                dst.extend_from_slice(&[0u8; 5][..]);
593
594                let mut length = 0u16;
595                let len_pos = dst.len();
596
597                dst.put_u16_le(length);
598
599                for chr in s.encode_utf16() {
600                    length += 1;
601                    dst.put_u16_le(chr);
602                }
603
604                let dst: &mut [u8] = dst.borrow_mut();
605                let bytes = (length * 2).to_le_bytes(); // u16, two bytes
606
607                for (i, byte) in bytes.iter().enumerate() {
608                    dst[len_pos + i] = *byte;
609                }
610            }
611            (ColumnData::String(Some(ref s)), None) => {
612                // length: 0xffff and raw collation
613                dst.put_u8(VarLenType::NVarchar as u8);
614                dst.extend_from_slice(&[0xff_u8; 2]);
615                dst.extend_from_slice(&[0u8; 5]);
616
617                // we cannot cheaply predetermine the length of the UCS2 string beforehand
618                // (2 * bytes(UTF8) is not always right) - so just let the SQL server handle it
619                dst.put_u64_le(0xfffffffffffffffe_u64);
620
621                // Write the varchar length
622                let mut length = 0u32;
623                let len_pos = dst.len();
624
625                dst.put_u32_le(length);
626
627                for chr in s.encode_utf16() {
628                    length += 1;
629                    dst.put_u16_le(chr);
630                }
631
632                if length > 0 {
633                    // PLP_TERMINATOR
634                    dst.put_u32_le(0);
635                }
636
637                let dst: &mut [u8] = dst.borrow_mut();
638                let bytes = (length * 2).to_le_bytes(); // u32, four bytes
639
640                for (i, byte) in bytes.iter().enumerate() {
641                    dst[len_pos + i] = *byte;
642                }
643            }
644            (ColumnData::Binary(opt), Some(TypeInfo::VarLenSized(vlc)))
645                if vlc.r#type() == VarLenType::BigBinary
646                    || vlc.r#type() == VarLenType::BigVarBin =>
647            {
648                if let Some(bytes) = opt {
649                    if bytes.len() > vlc.len() {
650                        return Err(crate::Error::BulkInput(
651                            format!(
652                                "Binary length {} exceed column limit {}",
653                                bytes.len(),
654                                vlc.len()
655                            )
656                            .into(),
657                        ));
658                    }
659
660                    if vlc.len() < 0xffff {
661                        dst.put_u16_le(bytes.len() as u16);
662                        dst.extend(bytes.into_owned());
663                    } else {
664                        // unknown size
665                        dst.put_u64_le(0xfffffffffffffffe);
666                        dst.put_u32_le(bytes.len() as u32);
667
668                        if !bytes.is_empty() {
669                            dst.extend(bytes.into_owned());
670                            dst.put_u32_le(0);
671                        }
672                    }
673                } else if vlc.len() < 0xffff {
674                    dst.put_u16_le(0xffff);
675                } else {
676                    dst.put_u64_le(0xffffffffffffffff);
677                }
678            }
679            (ColumnData::Binary(Some(bytes)), None) if bytes.len() <= 8000 => {
680                dst.put_u8(VarLenType::BigVarBin as u8);
681                dst.put_u16_le(8000);
682                dst.put_u16_le(bytes.len() as u16);
683                dst.extend(bytes.into_owned());
684            }
685            (ColumnData::Binary(Some(bytes)), None) => {
686                dst.put_u8(VarLenType::BigVarBin as u8);
687                // Max length
688                dst.put_u16_le(0xffff_u16);
689                // Also the length is unknown
690                dst.put_u64_le(0xfffffffffffffffe_u64);
691                // We'll write in one chunk, length is the whole bytes length
692                dst.put_u32_le(bytes.len() as u32);
693
694                if !bytes.is_empty() {
695                    // Payload
696                    dst.extend(bytes.into_owned());
697                    // PLP_TERMINATOR
698                    dst.put_u32_le(0);
699                }
700            }
701            (ColumnData::DateTime(opt), Some(TypeInfo::VarLenSized(vlc)))
702                if vlc.r#type() == VarLenType::Datetimen =>
703            {
704                if let Some(dt) = opt {
705                    dst.put_u8(8);
706                    dt.encode(dst)?;
707                } else {
708                    dst.put_u8(0);
709                }
710            }
711            (ColumnData::DateTime(Some(dt)), Some(TypeInfo::FixedLen(FixedLenType::Datetime))) => {
712                dt.encode(dst)?;
713            }
714            (ColumnData::DateTime(Some(dt)), None) => {
715                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 8, 8]);
716                dt.encode(&mut *dst)?;
717            }
718            (ColumnData::SmallDateTime(opt), Some(TypeInfo::VarLenSized(vlc)))
719                if vlc.r#type() == VarLenType::Datetimen =>
720            {
721                if let Some(dt) = opt {
722                    dst.put_u8(4);
723                    dt.encode(dst)?;
724                } else {
725                    dst.put_u8(0);
726                }
727            }
728            (
729                ColumnData::SmallDateTime(Some(dt)),
730                Some(TypeInfo::FixedLen(FixedLenType::Datetime4)),
731            ) => {
732                dt.encode(dst)?;
733            }
734            (ColumnData::SmallDateTime(Some(dt)), None) => {
735                dst.extend_from_slice(&[VarLenType::Datetimen as u8, 4, 4]);
736                dt.encode(&mut *dst)?;
737            }
738            #[cfg(feature = "tds73")]
739            (ColumnData::Date(opt), Some(TypeInfo::VarLenSized(vlc)))
740                if vlc.r#type() == VarLenType::Daten =>
741            {
742                if let Some(dt) = opt {
743                    dst.put_u8(3);
744                    dt.encode(dst)?;
745                } else {
746                    dst.put_u8(0);
747                }
748            }
749            #[cfg(feature = "tds73")]
750            (ColumnData::Date(Some(date)), None) => {
751                dst.extend_from_slice(&[VarLenType::Daten as u8, 3]);
752                date.encode(&mut *dst)?;
753            }
754            #[cfg(feature = "tds73")]
755            (ColumnData::Time(opt), Some(TypeInfo::VarLenSized(vlc)))
756                if vlc.r#type() == VarLenType::Timen =>
757            {
758                if let Some(time) = opt {
759                    dst.put_u8(time.len()?);
760                    time.encode(dst)?;
761                } else {
762                    dst.put_u8(0);
763                }
764            }
765            #[cfg(feature = "tds73")]
766            (ColumnData::Time(Some(time)), None) => {
767                dst.extend_from_slice(&[VarLenType::Timen as u8, time.scale(), time.len()?]);
768                time.encode(&mut *dst)?;
769            }
770            #[cfg(feature = "tds73")]
771            (ColumnData::DateTime2(opt), Some(TypeInfo::VarLenSized(vlc)))
772                if vlc.r#type() == VarLenType::Datetimen =>
773            {
774                if let Some(dt2) = opt {
775                    let dt = datetime2_to_datetime(&dt2)?;
776                    dst.put_u8(8);
777                    dt.encode(dst)?;
778                } else {
779                    dst.put_u8(0);
780                }
781            }
782            #[cfg(feature = "tds73")]
783            (ColumnData::DateTime2(opt), Some(TypeInfo::VarLenSized(vlc)))
784                if vlc.r#type() == VarLenType::Datetime2 =>
785            {
786                if let Some(mut dt2) = opt {
787                    if dt2.time().scale() != vlc.len() as u8 {
788                        let time = dt2.time();
789                        let increments = (time.increments() as f64
790                            * 10_f64.powi(vlc.len() as i32 - time.scale() as i32))
791                            as u64;
792                        dt2 = DateTime2::new(dt2.date(), Time::new(increments, vlc.len() as u8));
793                    }
794                    dst.put_u8(dt2.time().len()? + 3);
795                    dt2.encode(dst)?;
796                } else {
797                    dst.put_u8(0);
798                }
799            }
800            #[cfg(feature = "tds73")]
801            (ColumnData::DateTime2(Some(dt)), None) => {
802                let len = dt.time().len()? + 3;
803                dst.extend_from_slice(&[VarLenType::Datetime2 as u8, dt.time().scale(), len]);
804                dt.encode(&mut *dst)?;
805            }
806            #[cfg(feature = "tds73")]
807            (ColumnData::DateTimeOffset(opt), Some(TypeInfo::VarLenSized(vlc)))
808                if vlc.r#type() == VarLenType::DatetimeOffsetn =>
809            {
810                if let Some(dto) = opt {
811                    dst.put_u8(dto.datetime2().time().len()? + 5);
812                    dto.encode(dst)?;
813                } else {
814                    dst.put_u8(0);
815                }
816            }
817            #[cfg(feature = "tds73")]
818            (ColumnData::DateTimeOffset(Some(dto)), None) => {
819                let headers = [
820                    VarLenType::DatetimeOffsetn as u8,
821                    dto.datetime2().time().scale(),
822                    dto.datetime2().time().len()? + 5,
823                ];
824
825                dst.extend_from_slice(&headers);
826                dto.encode(&mut *dst)?;
827            }
828            (ColumnData::Xml(opt), Some(TypeInfo::Xml { .. })) => {
829                if let Some(xml) = opt {
830                    xml.into_owned().encode(dst)?;
831                } else {
832                    dst.put_u64_le(0xffffffffffffffff_u64);
833                }
834            }
835            (ColumnData::Xml(Some(xml)), None) => {
836                dst.put_u8(VarLenType::Xml as u8);
837                dst.put_u8(0);
838                xml.into_owned().encode(&mut *dst)?;
839            }
840            (ColumnData::Numeric(opt), Some(TypeInfo::VarLenSized(vlc)))
841                if vlc.r#type() == VarLenType::Money =>
842            {
843                if let Some(num) = opt {
844                    money::encode(dst, vlc.len(), f64::from(num));
845                } else {
846                    dst.put_u8(0);
847                }
848            }
849            (ColumnData::Numeric(opt), Some(TypeInfo::VarLenSizedPrecision { ty, scale, .. }))
850                if ty == &VarLenType::Numericn || ty == &VarLenType::Decimaln =>
851            {
852                if let Some(num) = opt {
853                    // The value is sent at the column's scale (the scale lives in
854                    // the TYPE_INFO, not the value), so rescale when the client
855                    // value's scale differs from the target column's scale.
856                    let target_scale = *scale;
857                    let num = if target_scale == num.scale() {
858                        num
859                    } else if target_scale > num.scale() {
860                        // Scale up: multiply, checking for i128 overflow.
861                        let factor = 10i128.pow((target_scale - num.scale()) as u32);
862                        let value = num.value().checked_mul(factor).ok_or_else(|| {
863                            crate::Error::Conversion(
864                                "numeric value overflows when scaling to the column's scale".into(),
865                            )
866                        })?;
867                        Numeric::new_with_scale(value, target_scale)
868                    } else {
869                        // Scale down: divide, rounding half away from zero
870                        // (this loses precision beyond the column's scale).
871                        let factor = 10i128.pow((num.scale() - target_scale) as u32);
872                        let half = factor / 2;
873                        let v = num.value();
874                        let value = if v >= 0 {
875                            (v + half) / factor
876                        } else {
877                            (v - half) / factor
878                        };
879                        Numeric::new_with_scale(value, target_scale)
880                    };
881                    num.encode(&mut *dst)?;
882                } else {
883                    dst.put_u8(0);
884                }
885            }
886            (ColumnData::Numeric(Some(num)), None) => {
887                let headers = &[
888                    VarLenType::Numericn as u8,
889                    num.len(),
890                    num.precision(),
891                    num.scale(),
892                ];
893
894                dst.extend_from_slice(headers);
895                num.encode(&mut *dst)?;
896            }
897            (data, Some(TypeInfo::VarLenSized(vlc))) if vlc.r#type() == VarLenType::SSVariant => {
898                sql_variant::encode(&mut *dst, data)?;
899            }
900            (_, None) => {
901                // None/null
902                dst.put_u8(FixedLenType::Null as u8);
903            }
904            (v, ref ti) => {
905                return Err(crate::Error::BulkInput(
906                    format!("invalid data type, expecting {:?} but found {:?}", ti, v).into(),
907                ));
908            }
909        }
910
911        Ok(())
912    }
913}
914
915/// Reads a [`ColumnData`] straight out of a row without any conversion,
916/// borrowing from the row's owned data.
917impl<'a> FromSql<'a> for ColumnData<'a> {
918    fn from_sql(value: &'a ColumnData<'static>) -> crate::Result<Option<Self>> {
919        Ok(Some(value.clone()))
920    }
921}
922
923/// Reads a [`ColumnData`] straight out of a row without any conversion, taking
924/// ownership of the value.
925impl<'a> FromSqlOwned for ColumnData<'a> {
926    fn from_sql_owned(value: ColumnData<'static>) -> crate::Result<Option<Self>> {
927        Ok(Some(value))
928    }
929}
930
931/// Binds a [`ColumnData`] directly as a query parameter, by reference.
932impl<'a> ToSql for ColumnData<'a> {
933    fn to_sql(&self) -> ColumnData<'_> {
934        self.clone()
935    }
936}
937
938/// Binds a [`ColumnData`] directly as a query parameter, by value.
939impl<'a> IntoSql<'a> for ColumnData<'a> {
940    fn into_sql(self) -> ColumnData<'a> {
941        self
942    }
943}
944
945#[cfg(test)]
946mod tests {
947    use super::*;
948    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
949    use crate::tds::Collation;
950    use crate::{Error, VarLenContext};
951    use bytes::BytesMut;
952
953    async fn test_round_trip(ti: TypeInfo, d: ColumnData<'_>) {
954        let mut buf = BytesMut::new();
955        let mut buf_with_ti = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
956
957        d.clone()
958            .encode(&mut buf_with_ti)
959            .expect("encode must succeed");
960
961        let reader = &mut buf.into_sql_read_bytes();
962        let nd = ColumnData::decode(reader, &ti)
963            .await
964            .expect("decode must succeed");
965
966        assert_eq!(nd, d);
967
968        reader
969            .read_u8()
970            .await
971            .expect_err("decode must consume entire buffer");
972    }
973
974    #[test]
975    fn type_name_maps_each_variant() {
976        assert_eq!(ColumnData::U8(Some(1)).type_name(), "tinyint");
977        assert_eq!(ColumnData::I16(Some(1)).type_name(), "smallint");
978        assert_eq!(ColumnData::I32(Some(1)).type_name(), "int");
979        assert_eq!(ColumnData::I64(Some(1)).type_name(), "bigint");
980        assert_eq!(ColumnData::F32(Some(1.0)).type_name(), "float(24)");
981        assert_eq!(ColumnData::F64(Some(1.0)).type_name(), "float(53)");
982        assert_eq!(ColumnData::Bit(Some(true)).type_name(), "bit");
983        assert_eq!(ColumnData::Guid(None).type_name(), "uniqueidentifier");
984        assert_eq!(ColumnData::Numeric(None).type_name(), "numeric");
985        assert_eq!(ColumnData::DateTime(None).type_name(), "datetime");
986        assert_eq!(ColumnData::SmallDateTime(None).type_name(), "smalldatetime");
987    }
988
989    #[test]
990    fn type_name_string_length_thresholds() {
991        // None and anything up to 4000 chars is a sized nvarchar; just past it
992        // becomes nvarchar(max). The `<= 4000` and `<= MAX_NVARCHAR_SIZE` guards
993        // each flip the answer at their boundary.
994        assert_eq!(ColumnData::String(None).type_name(), "nvarchar(4000)");
995        assert_eq!(
996            ColumnData::String(Some("a".repeat(100).into())).type_name(),
997            "nvarchar(4000)"
998        );
999        assert_eq!(
1000            ColumnData::String(Some("a".repeat(4000).into())).type_name(),
1001            "nvarchar(4000)"
1002        );
1003        assert_eq!(
1004            ColumnData::String(Some("a".repeat(4001).into())).type_name(),
1005            "nvarchar(max)"
1006        );
1007    }
1008
1009    #[test]
1010    fn type_name_binary_length_threshold() {
1011        assert_eq!(
1012            ColumnData::Binary(Some(vec![0u8; 8000].into())).type_name(),
1013            "varbinary(8000)"
1014        );
1015        assert_eq!(
1016            ColumnData::Binary(Some(vec![0u8; 8001].into())).type_name(),
1017            "varbinary(max)"
1018        );
1019        assert_eq!(ColumnData::Binary(None).type_name(), "varbinary(max)");
1020    }
1021
1022    #[test]
1023    #[cfg(feature = "serde")]
1024    fn serde_json_round_trip() {
1025        let value = ColumnData::I32(Some(1234));
1026        let json = serde_json::to_string(&value).expect("serialize");
1027        let back: ColumnData<'static> = serde_json::from_str(&json).expect("deserialize");
1028        assert_eq!(value, back);
1029    }
1030
1031    #[tokio::test]
1032    async fn i32_with_varlen_int() {
1033        test_round_trip(
1034            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
1035            ColumnData::I32(Some(42)),
1036        )
1037        .await;
1038    }
1039
1040    #[tokio::test]
1041    async fn none_with_varlen_int() {
1042        test_round_trip(
1043            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
1044            ColumnData::I32(None),
1045        )
1046        .await;
1047    }
1048
1049    #[tokio::test]
1050    async fn i32_with_fixedlen_int() {
1051        test_round_trip(
1052            TypeInfo::FixedLen(FixedLenType::Int4),
1053            ColumnData::I32(Some(42)),
1054        )
1055        .await;
1056    }
1057
1058    #[tokio::test]
1059    async fn bit_with_varlen_bit() {
1060        test_round_trip(
1061            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Bitn, 1, None)),
1062            ColumnData::Bit(Some(true)),
1063        )
1064        .await;
1065    }
1066
1067    #[tokio::test]
1068    async fn none_with_varlen_bit() {
1069        test_round_trip(
1070            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Bitn, 1, None)),
1071            ColumnData::Bit(None),
1072        )
1073        .await;
1074    }
1075
1076    #[tokio::test]
1077    async fn bit_with_fixedlen_bit() {
1078        test_round_trip(
1079            TypeInfo::FixedLen(FixedLenType::Bit),
1080            ColumnData::Bit(Some(true)),
1081        )
1082        .await;
1083    }
1084
1085    #[tokio::test]
1086    async fn u8_with_varlen_int() {
1087        test_round_trip(
1088            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
1089            ColumnData::U8(Some(8u8)),
1090        )
1091        .await;
1092    }
1093
1094    #[tokio::test]
1095    async fn none_u8_with_varlen_int() {
1096        test_round_trip(
1097            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
1098            ColumnData::U8(None),
1099        )
1100        .await;
1101    }
1102
1103    #[tokio::test]
1104    async fn u8_with_fixedlen_int() {
1105        test_round_trip(
1106            TypeInfo::FixedLen(FixedLenType::Int1),
1107            ColumnData::U8(Some(8u8)),
1108        )
1109        .await;
1110    }
1111
1112    #[tokio::test]
1113    async fn i16_with_varlen_intn() {
1114        test_round_trip(
1115            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
1116            ColumnData::I16(Some(8i16)),
1117        )
1118        .await;
1119    }
1120
1121    #[tokio::test]
1122    async fn none_i16_with_varlen_intn() {
1123        test_round_trip(
1124            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
1125            ColumnData::I16(None),
1126        )
1127        .await;
1128    }
1129
1130    #[tokio::test]
1131    async fn none_with_varlen_intn() {
1132        test_round_trip(
1133            TypeInfo::FixedLen(FixedLenType::Int2),
1134            ColumnData::I16(Some(8i16)),
1135        )
1136        .await;
1137    }
1138
1139    #[tokio::test]
1140    async fn i64_with_varlen_intn() {
1141        test_round_trip(
1142            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
1143            ColumnData::I64(Some(8i64)),
1144        )
1145        .await;
1146    }
1147
1148    #[tokio::test]
1149    async fn i64_none_with_varlen_intn() {
1150        test_round_trip(
1151            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
1152            ColumnData::I64(None),
1153        )
1154        .await;
1155    }
1156
1157    #[tokio::test]
1158    async fn i64_with_fixedlen_int8() {
1159        test_round_trip(
1160            TypeInfo::FixedLen(FixedLenType::Int8),
1161            ColumnData::I64(Some(8i64)),
1162        )
1163        .await;
1164    }
1165
1166    #[tokio::test]
1167    async fn f32_with_varlen_floatn() {
1168        test_round_trip(
1169            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1170            ColumnData::F32(Some(8f32)),
1171        )
1172        .await;
1173    }
1174
1175    #[tokio::test]
1176    async fn null_f32_with_varlen_floatn() {
1177        test_round_trip(
1178            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1179            ColumnData::F32(None),
1180        )
1181        .await;
1182    }
1183
1184    #[tokio::test]
1185    async fn f32_with_fixedlen_float4() {
1186        test_round_trip(
1187            TypeInfo::FixedLen(FixedLenType::Float4),
1188            ColumnData::F32(Some(8f32)),
1189        )
1190        .await;
1191    }
1192
1193    #[tokio::test]
1194    async fn f64_with_varlen_floatn() {
1195        test_round_trip(
1196            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
1197            ColumnData::F64(Some(8f64)),
1198        )
1199        .await;
1200    }
1201
1202    #[tokio::test]
1203    async fn none_f64_with_varlen_floatn() {
1204        test_round_trip(
1205            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
1206            ColumnData::F64(None),
1207        )
1208        .await;
1209    }
1210
1211    #[tokio::test]
1212    async fn f64_with_fixedlen_float8() {
1213        test_round_trip(
1214            TypeInfo::FixedLen(FixedLenType::Float8),
1215            ColumnData::F64(Some(8f64)),
1216        )
1217        .await;
1218    }
1219
1220    #[tokio::test]
1221    async fn guid_with_varlen_guid() {
1222        test_round_trip(
1223            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
1224            ColumnData::Guid(Some(Uuid::new_v4())),
1225        )
1226        .await;
1227    }
1228
1229    #[tokio::test]
1230    async fn none_guid_with_varlen_guid() {
1231        test_round_trip(
1232            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
1233            ColumnData::Guid(None),
1234        )
1235        .await;
1236    }
1237
1238    #[tokio::test]
1239    async fn numeric_with_varlen_sized_precision() {
1240        test_round_trip(
1241            TypeInfo::VarLenSizedPrecision {
1242                ty: VarLenType::Numericn,
1243                size: 17,
1244                precision: 18,
1245                scale: 0,
1246            },
1247            ColumnData::Numeric(Some(Numeric::new_with_scale(23, 0))),
1248        )
1249        .await;
1250    }
1251
1252    #[tokio::test]
1253    async fn none_numeric_with_varlen_sized_precision() {
1254        test_round_trip(
1255            TypeInfo::VarLenSizedPrecision {
1256                ty: VarLenType::Numericn,
1257                size: 17,
1258                precision: 18,
1259                scale: 0,
1260            },
1261            ColumnData::Numeric(None),
1262        )
1263        .await;
1264    }
1265
1266    #[tokio::test]
1267    async fn string_with_varlen_bigchar() {
1268        test_round_trip(
1269            TypeInfo::VarLenSized(VarLenContext::new(
1270                VarLenType::BigChar,
1271                40,
1272                Some(Collation::new(13632521, 52)),
1273            )),
1274            ColumnData::String(Some("aaa".into())),
1275        )
1276        .await;
1277    }
1278
1279    #[tokio::test]
1280    async fn long_string_with_varlen_bigchar() {
1281        test_round_trip(
1282            TypeInfo::VarLenSized(VarLenContext::new(
1283                VarLenType::BigChar,
1284                0x8ffff,
1285                Some(Collation::new(13632521, 52)),
1286            )),
1287            ColumnData::String(Some("aaa".into())),
1288        )
1289        .await;
1290    }
1291
1292    #[tokio::test]
1293    async fn none_long_string_with_varlen_bigchar() {
1294        test_round_trip(
1295            TypeInfo::VarLenSized(VarLenContext::new(
1296                VarLenType::BigChar,
1297                0x8ffff,
1298                Some(Collation::new(13632521, 52)),
1299            )),
1300            ColumnData::String(None),
1301        )
1302        .await;
1303    }
1304
1305    #[tokio::test]
1306    async fn none_string_with_varlen_bigchar() {
1307        test_round_trip(
1308            TypeInfo::VarLenSized(VarLenContext::new(
1309                VarLenType::BigChar,
1310                40,
1311                Some(Collation::new(13632521, 52)),
1312            )),
1313            ColumnData::String(None),
1314        )
1315        .await;
1316    }
1317
1318    #[tokio::test]
1319    async fn string_with_varlen_bigvarchar() {
1320        test_round_trip(
1321            TypeInfo::VarLenSized(VarLenContext::new(
1322                VarLenType::BigVarChar,
1323                40,
1324                Some(Collation::new(13632521, 52)),
1325            )),
1326            ColumnData::String(Some("aaa".into())),
1327        )
1328        .await;
1329    }
1330
1331    #[tokio::test]
1332    async fn none_string_with_varlen_bigvarchar() {
1333        test_round_trip(
1334            TypeInfo::VarLenSized(VarLenContext::new(
1335                VarLenType::BigVarChar,
1336                40,
1337                Some(Collation::new(13632521, 52)),
1338            )),
1339            ColumnData::String(None),
1340        )
1341        .await;
1342    }
1343
1344    #[tokio::test]
1345    async fn empty_string_with_varlen_bigvarchar() {
1346        test_round_trip(
1347            TypeInfo::VarLenSized(VarLenContext::new(
1348                VarLenType::BigVarChar,
1349                0x8ffff,
1350                Some(Collation::new(13632521, 52)),
1351            )),
1352            ColumnData::String(Some("".into())),
1353        )
1354        .await;
1355    }
1356
1357    #[tokio::test]
1358    async fn string_with_varlen_nvarchar() {
1359        test_round_trip(
1360            TypeInfo::VarLenSized(VarLenContext::new(
1361                VarLenType::NVarchar,
1362                40,
1363                Some(Collation::new(13632521, 52)),
1364            )),
1365            ColumnData::String(Some("hhh".into())),
1366        )
1367        .await;
1368    }
1369
1370    #[tokio::test]
1371    async fn none_string_with_varlen_nvarchar() {
1372        test_round_trip(
1373            TypeInfo::VarLenSized(VarLenContext::new(
1374                VarLenType::NVarchar,
1375                40,
1376                Some(Collation::new(13632521, 52)),
1377            )),
1378            ColumnData::String(None),
1379        )
1380        .await;
1381    }
1382
1383    #[tokio::test]
1384    async fn empty_string_with_varlen_nvarchar() {
1385        test_round_trip(
1386            TypeInfo::VarLenSized(VarLenContext::new(
1387                VarLenType::NVarchar,
1388                0x8ffff,
1389                Some(Collation::new(13632521, 52)),
1390            )),
1391            ColumnData::String(Some("".into())),
1392        )
1393        .await;
1394    }
1395
1396    #[tokio::test]
1397    async fn string_with_varlen_nchar() {
1398        test_round_trip(
1399            TypeInfo::VarLenSized(VarLenContext::new(
1400                VarLenType::NChar,
1401                40,
1402                Some(Collation::new(13632521, 52)),
1403            )),
1404            ColumnData::String(Some("hhh".into())),
1405        )
1406        .await;
1407    }
1408
1409    #[tokio::test]
1410    async fn long_string_with_varlen_nchar() {
1411        test_round_trip(
1412            TypeInfo::VarLenSized(VarLenContext::new(
1413                VarLenType::NChar,
1414                0x8ffff,
1415                Some(Collation::new(13632521, 52)),
1416            )),
1417            ColumnData::String(Some("hhh".into())),
1418        )
1419        .await;
1420    }
1421
1422    #[tokio::test]
1423    async fn none_long_string_with_varlen_nchar() {
1424        test_round_trip(
1425            TypeInfo::VarLenSized(VarLenContext::new(
1426                VarLenType::NChar,
1427                0x8ffff,
1428                Some(Collation::new(13632521, 52)),
1429            )),
1430            ColumnData::String(None),
1431        )
1432        .await;
1433    }
1434
1435    #[tokio::test]
1436    async fn none_string_with_varlen_nchar() {
1437        test_round_trip(
1438            TypeInfo::VarLenSized(VarLenContext::new(
1439                VarLenType::NChar,
1440                40,
1441                Some(Collation::new(13632521, 52)),
1442            )),
1443            ColumnData::String(None),
1444        )
1445        .await;
1446    }
1447
1448    #[tokio::test]
1449    async fn binary_with_varlen_bigbinary() {
1450        test_round_trip(
1451            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 40, None)),
1452            ColumnData::Binary(Some(b"aaa".as_slice().into())),
1453        )
1454        .await;
1455    }
1456
1457    #[tokio::test]
1458    async fn long_binary_with_varlen_bigbinary() {
1459        test_round_trip(
1460            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 0x8ffff, None)),
1461            ColumnData::Binary(Some(b"aaa".as_slice().into())),
1462        )
1463        .await;
1464    }
1465
1466    #[tokio::test]
1467    async fn none_binary_with_varlen_bigbinary() {
1468        test_round_trip(
1469            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 40, None)),
1470            ColumnData::Binary(None),
1471        )
1472        .await;
1473    }
1474
1475    #[tokio::test]
1476    async fn none_long_binary_with_varlen_bigbinary() {
1477        test_round_trip(
1478            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 0x8ffff, None)),
1479            ColumnData::Binary(None),
1480        )
1481        .await;
1482    }
1483
1484    #[tokio::test]
1485    async fn binary_with_varlen_bigvarbin() {
1486        test_round_trip(
1487            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 40, None)),
1488            ColumnData::Binary(Some(b"aaa".as_slice().into())),
1489        )
1490        .await;
1491    }
1492
1493    #[tokio::test]
1494    async fn none_binary_with_varlen_bigvarbin() {
1495        test_round_trip(
1496            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 40, None)),
1497            ColumnData::Binary(None),
1498        )
1499        .await;
1500    }
1501
1502    #[tokio::test]
1503    async fn empty_binary_with_varlen_bigvarbin() {
1504        test_round_trip(
1505            TypeInfo::VarLenSized(VarLenContext::new(
1506                VarLenType::BigVarBin,
1507                0x8ffff,
1508                Some(Collation::new(13632521, 52)),
1509            )),
1510            ColumnData::Binary(Some(b"".as_slice().into())),
1511        )
1512        .await;
1513    }
1514
1515    #[tokio::test]
1516    async fn datetime_with_varlen_datetimen() {
1517        test_round_trip(
1518            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None)),
1519            ColumnData::DateTime(Some(DateTime::new(200, 3000))),
1520        )
1521        .await;
1522    }
1523
1524    // this is inconsistent: decode will decode any None datetime to smalldatetime, ignoring size
1525    // but it's non-critical, so let it be here
1526    #[tokio::test]
1527    async fn none_datetime_with_varlen_datetimen() {
1528        test_round_trip(
1529            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None)),
1530            ColumnData::DateTime(None),
1531        )
1532        .await;
1533    }
1534
1535    #[tokio::test]
1536    async fn datetime_with_fixedlen_datetime() {
1537        test_round_trip(
1538            TypeInfo::FixedLen(FixedLenType::Datetime),
1539            ColumnData::DateTime(Some(DateTime::new(200, 3000))),
1540        )
1541        .await;
1542    }
1543
1544    #[tokio::test]
1545    async fn smalldatetime_with_varlen_datetimen() {
1546        test_round_trip(
1547            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 4, None)),
1548            ColumnData::SmallDateTime(Some(SmallDateTime::new(200, 3000))),
1549        )
1550        .await;
1551    }
1552
1553    #[tokio::test]
1554    async fn none_smalldatetime_with_varlen_datetimen() {
1555        test_round_trip(
1556            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 4, None)),
1557            ColumnData::SmallDateTime(None),
1558        )
1559        .await;
1560    }
1561
1562    #[tokio::test]
1563    async fn smalldatetime_with_fixedlen_datetime4() {
1564        test_round_trip(
1565            TypeInfo::FixedLen(FixedLenType::Datetime4),
1566            ColumnData::SmallDateTime(Some(SmallDateTime::new(200, 3000))),
1567        )
1568        .await;
1569    }
1570
1571    #[cfg(feature = "tds73")]
1572    #[tokio::test]
1573    async fn date_with_varlen_daten() {
1574        test_round_trip(
1575            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Daten, 3, None)),
1576            ColumnData::Date(Some(Date::new(200))),
1577        )
1578        .await;
1579    }
1580
1581    #[cfg(feature = "tds73")]
1582    #[tokio::test]
1583    async fn none_date_with_varlen_daten() {
1584        test_round_trip(
1585            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Daten, 3, None)),
1586            ColumnData::Date(None),
1587        )
1588        .await;
1589    }
1590
1591    #[cfg(feature = "tds73")]
1592    #[tokio::test]
1593    async fn time_with_varlen_timen() {
1594        test_round_trip(
1595            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 7, None)),
1596            ColumnData::Time(Some(Time::new(55, 7))),
1597        )
1598        .await;
1599    }
1600
1601    #[cfg(feature = "tds73")]
1602    #[tokio::test]
1603    async fn none_time_with_varlen_timen() {
1604        test_round_trip(
1605            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 7, None)),
1606            ColumnData::Time(None),
1607        )
1608        .await;
1609    }
1610
1611    #[cfg(feature = "tds73")]
1612    #[tokio::test]
1613    async fn datetime2_with_varlen_datetime2() {
1614        test_round_trip(
1615            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetime2, 7, None)),
1616            ColumnData::DateTime2(Some(DateTime2::new(Date::new(55), Time::new(222, 7)))),
1617        )
1618        .await;
1619    }
1620
1621    #[cfg(feature = "tds73")]
1622    #[tokio::test]
1623    async fn none_datetime2_with_varlen_datetime2() {
1624        test_round_trip(
1625            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetime2, 7, None)),
1626            ColumnData::DateTime2(None),
1627        )
1628        .await;
1629    }
1630
1631    #[cfg(feature = "tds73")]
1632    #[tokio::test]
1633    async fn datetimeoffset_with_varlen_datetimeoffsetn() {
1634        test_round_trip(
1635            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 7, None)),
1636            ColumnData::DateTimeOffset(Some(DateTimeOffset::new(
1637                DateTime2::new(Date::new(55), Time::new(222, 7)),
1638                -8,
1639            ))),
1640        )
1641        .await;
1642    }
1643
1644    #[cfg(feature = "tds73")]
1645    #[tokio::test]
1646    async fn none_datetimeoffset_with_varlen_datetimeoffsetn() {
1647        test_round_trip(
1648            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 7, None)),
1649            ColumnData::DateTimeOffset(None),
1650        )
1651        .await;
1652    }
1653
1654    #[cfg(feature = "tds73")]
1655    #[tokio::test]
1656    async fn xml_with_xml() {
1657        test_round_trip(
1658            TypeInfo::Xml {
1659                schema: None,
1660                size: 0xfffffffffffffffe_usize,
1661            },
1662            ColumnData::Xml(Some(Cow::Owned(XmlData::new("<a>ddd</a>")))),
1663        )
1664        .await;
1665    }
1666
1667    #[cfg(feature = "tds73")]
1668    #[tokio::test]
1669    async fn none_xml_with_xml() {
1670        test_round_trip(
1671            TypeInfo::Xml {
1672                schema: None,
1673                size: 0xfffffffffffffffe_usize,
1674            },
1675            ColumnData::Xml(None),
1676        )
1677        .await;
1678    }
1679
1680    #[tokio::test]
1681    async fn invalid_type_fails() {
1682        let data = vec![
1683            (
1684                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1685                ColumnData::I32(Some(42)),
1686            ),
1687            (
1688                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
1689                ColumnData::I32(None),
1690            ),
1691            (
1692                TypeInfo::FixedLen(FixedLenType::Int4),
1693                ColumnData::I32(None),
1694            ),
1695        ];
1696
1697        for (ti, d) in data {
1698            let mut buf = BytesMut::new();
1699            let mut buf_ti = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
1700
1701            let err = d.encode(&mut buf_ti).expect_err("encode should fail");
1702
1703            if let Error::BulkInput(_) = err {
1704            } else {
1705                panic!("Expected: Error::BulkInput, got: {:?}", err);
1706            }
1707        }
1708    }
1709
1710    #[test]
1711    fn column_data_from_sql_clones_by_reference() {
1712        let value: ColumnData<'static> = ColumnData::I32(Some(42));
1713        let out = ColumnData::from_sql(&value).unwrap();
1714        assert_eq!(out, Some(ColumnData::I32(Some(42))));
1715    }
1716
1717    #[test]
1718    fn column_data_from_sql_owned_passes_through() {
1719        let value: ColumnData<'static> = ColumnData::String(Some(Cow::Borrowed("hello")));
1720        let out = ColumnData::from_sql_owned(value).unwrap();
1721        assert_eq!(out, Some(ColumnData::String(Some(Cow::Borrowed("hello")))));
1722    }
1723
1724    #[test]
1725    fn column_data_to_sql_clones_by_reference() {
1726        let value = ColumnData::Bit(Some(true));
1727        assert_eq!(value.to_sql(), ColumnData::Bit(Some(true)));
1728        // Original is untouched.
1729        assert_eq!(value, ColumnData::Bit(Some(true)));
1730    }
1731
1732    #[test]
1733    fn column_data_into_sql_passes_through() {
1734        let value = ColumnData::F64(Some(1.5));
1735        assert_eq!(value.into_sql(), ColumnData::F64(Some(1.5)));
1736    }
1737
1738    #[cfg(feature = "tds73")]
1739    #[test]
1740    fn datetime2_to_datetime_conversion() {
1741        use crate::tds::time::{Date, DateTime2, Time};
1742
1743        // 2020-01-01 00:00:00 with scale 7 (100ns increments).
1744        // Days from year 1 to 2020-01-01 is 737_425.
1745        let dt2 = DateTime2::new(Date::new(737_425), Time::new(0, 7));
1746        let dt = datetime2_to_datetime(&dt2).expect("conversion must succeed");
1747
1748        assert_eq!(dt.days(), (737_425 - DAYS_YEAR_1_TO_1900) as i32);
1749        assert_eq!(dt.seconds_fragments(), 0);
1750
1751        // 12:00:00 exactly => half a day of 1/300s fragments.
1752        let noon_increments = 12u64 * 3600 * 10u64.pow(7);
1753        let dt2 = DateTime2::new(Date::new(737_425), Time::new(noon_increments, 7));
1754        let dt = datetime2_to_datetime(&dt2).expect("conversion must succeed");
1755
1756        assert_eq!(dt.seconds_fragments(), 12 * 3600 * 300);
1757
1758        // Dates earlier than 1900-01-01 cannot be represented by `datetime`.
1759        let dt2 = DateTime2::new(Date::new(0), Time::new(0, 7));
1760        assert!(datetime2_to_datetime(&dt2).is_err());
1761    }
1762
1763    // ----- helpers for the coverage tests below -----
1764
1765    fn encode_with_ti(ti: &TypeInfo, d: ColumnData<'_>) -> crate::Result<BytesMut> {
1766        let mut buf = BytesMut::new();
1767        {
1768            let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(ti);
1769            d.encode(&mut b)?;
1770        }
1771        Ok(buf)
1772    }
1773
1774    fn encode_without_ti(d: ColumnData<'_>) -> crate::Result<BytesMut> {
1775        let mut buf = BytesMut::new();
1776        {
1777            let mut b = BytesMutWithTypeInfo::new(&mut buf);
1778            d.encode(&mut b)?;
1779        }
1780        Ok(buf)
1781    }
1782
1783    fn expect_bulk_input(ti: TypeInfo, d: ColumnData<'_>) {
1784        let mut buf = BytesMut::new();
1785        let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
1786        let err = d.encode(&mut b).expect_err("encode should fail");
1787        assert!(matches!(err, Error::BulkInput(_)), "got {:?}", err);
1788    }
1789
1790    // NOTE: the `ntext(max)` catch-all in type_name is only reached by a string
1791    // longer than MAX_NVARCHAR_SIZE (>1 GiB); allocating that in a unit test is
1792    // impractical (slow / OOM-prone in CI), so that single line is intentionally
1793    // left uncovered.
1794
1795    // ----- decode: line 199 (VarLenSizedPrecision non-numeric -> todo!()) -----
1796
1797    #[tokio::test]
1798    #[should_panic]
1799    async fn decode_varlen_sized_precision_unsupported_panics() {
1800        let ti = TypeInfo::VarLenSizedPrecision {
1801            ty: VarLenType::Money,
1802            size: 8,
1803            precision: 0,
1804            scale: 0,
1805        };
1806        let buf = BytesMut::new();
1807        let reader = &mut buf.into_sql_read_bytes();
1808        let _ = ColumnData::decode(reader, &ti).await;
1809    }
1810
1811    // ----- decode: line 202 (Udt) -----
1812
1813    #[tokio::test]
1814    async fn decode_udt_type_info() {
1815        use bytes::BufMut;
1816
1817        let ti = TypeInfo::Udt(crate::tds::codec::type_info::UdtInfo {
1818            max_byte_size: 0xffff,
1819            db_name: "db".into(),
1820            schema_name: "dbo".into(),
1821            type_name: "geometry".into(),
1822            assembly_qualified_name: String::new(),
1823        });
1824
1825        let mut buf = BytesMut::new();
1826        // PLP unknown-length sentinel + one chunk + terminator.
1827        buf.put_u64_le(0xfffffffffffffffe);
1828        buf.put_u32_le(4);
1829        buf.extend_from_slice(&[1, 2, 3, 4]);
1830        buf.put_u32_le(0);
1831
1832        let reader = &mut buf.into_sql_read_bytes();
1833        let nd = ColumnData::decode(reader, &ti).await.unwrap();
1834        assert_eq!(nd, ColumnData::Binary(Some(vec![1, 2, 3, 4].into())));
1835    }
1836
1837    // ----- F64 with Money (lines 376-383) -----
1838
1839    #[tokio::test]
1840    async fn f64_with_varlen_money() {
1841        test_round_trip(
1842            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None)),
1843            ColumnData::F64(Some(3.5)),
1844        )
1845        .await;
1846    }
1847
1848    #[tokio::test]
1849    async fn none_f64_with_varlen_money() {
1850        test_round_trip(
1851            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None)),
1852            ColumnData::F64(None),
1853        )
1854        .await;
1855    }
1856
1857    // ----- BigChar error paths (lines 426, 430-437) -----
1858
1859    #[tokio::test]
1860    async fn bigchar_unrepresentable_character_errors() {
1861        let ti = TypeInfo::VarLenSized(VarLenContext::new(
1862            VarLenType::BigChar,
1863            40,
1864            Some(Collation::new(13632521, 52)),
1865        ));
1866        let mut buf = BytesMut::new();
1867        let mut b = BytesMutWithTypeInfo::new(&mut buf).with_type_info(&ti);
1868        // An emoji has no representation in the (single-byte) column collation.
1869        let err = ColumnData::String(Some("\u{1F600}".into()))
1870            .encode(&mut b)
1871            .expect_err("encode should fail");
1872        assert!(matches!(err, Error::Encoding(_)), "got {:?}", err);
1873    }
1874
1875    #[tokio::test]
1876    async fn bigchar_too_long_errors() {
1877        expect_bulk_input(
1878            TypeInfo::VarLenSized(VarLenContext::new(
1879                VarLenType::BigChar,
1880                2,
1881                Some(Collation::new(13632521, 52)),
1882            )),
1883            ColumnData::String(Some("aaa".into())),
1884        );
1885    }
1886
1887    // ----- NVarchar error paths (lines 481-488 small, 513-520 unknown-size) -----
1888
1889    #[tokio::test]
1890    async fn nvarchar_too_long_small_errors() {
1891        expect_bulk_input(
1892            TypeInfo::VarLenSized(VarLenContext::new(
1893                VarLenType::NVarchar,
1894                2,
1895                Some(Collation::new(13632521, 52)),
1896            )),
1897            ColumnData::String(Some("aaa".into())),
1898        );
1899    }
1900
1901    #[tokio::test]
1902    async fn nvarchar_too_long_unknown_size_errors() {
1903        // vlc.len() == 0xffff drives the unknown-size path; a string whose UTF-16
1904        // byte length exceeds the column limit trips the check at 513-520.
1905        expect_bulk_input(
1906            TypeInfo::VarLenSized(VarLenContext::new(
1907                VarLenType::NVarchar,
1908                0xffff,
1909                Some(Collation::new(13632521, 52)),
1910            )),
1911            ColumnData::String(Some("a".repeat(40_000).into())),
1912        );
1913    }
1914
1915    // ----- Text / NText encode arms (lines 538-587) -----
1916
1917    #[tokio::test]
1918    async fn string_with_varlen_text() {
1919        test_round_trip(
1920            TypeInfo::VarLenSized(VarLenContext::new(
1921                VarLenType::Text,
1922                40,
1923                Some(Collation::new(13632521, 52)),
1924            )),
1925            ColumnData::String(Some("hello".into())),
1926        )
1927        .await;
1928    }
1929
1930    #[tokio::test]
1931    async fn none_string_with_varlen_text() {
1932        test_round_trip(
1933            TypeInfo::VarLenSized(VarLenContext::new(
1934                VarLenType::Text,
1935                40,
1936                Some(Collation::new(13632521, 52)),
1937            )),
1938            ColumnData::String(None),
1939        )
1940        .await;
1941    }
1942
1943    #[tokio::test]
1944    async fn string_with_varlen_ntext() {
1945        test_round_trip(
1946            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NText, 40, None)),
1947            ColumnData::String(Some("hi".into())),
1948        )
1949        .await;
1950    }
1951
1952    #[tokio::test]
1953    async fn none_string_with_varlen_ntext() {
1954        test_round_trip(
1955            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NText, 40, None)),
1956            ColumnData::String(None),
1957        )
1958        .await;
1959    }
1960
1961    // ----- Binary too long (lines 650-657) -----
1962
1963    #[tokio::test]
1964    async fn binary_too_long_errors() {
1965        expect_bulk_input(
1966            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 2, None)),
1967            ColumnData::Binary(Some(b"aaa".as_slice().into())),
1968        );
1969    }
1970
1971    // ----- DateTime / SmallDateTime encode without TypeInfo (714-716, 734-736) -----
1972
1973    #[tokio::test]
1974    async fn datetime_encode_without_type_info() {
1975        let buf = encode_without_ti(ColumnData::DateTime(Some(DateTime::new(200, 3000)))).unwrap();
1976        assert_eq!(buf[0], VarLenType::Datetimen as u8);
1977        assert_eq!(buf[1], 8);
1978        assert_eq!(buf[2], 8);
1979    }
1980
1981    #[tokio::test]
1982    async fn smalldatetime_encode_without_type_info() {
1983        let buf = encode_without_ti(ColumnData::SmallDateTime(Some(SmallDateTime::new(
1984            200, 3000,
1985        ))))
1986        .unwrap();
1987        assert_eq!(buf[0], VarLenType::Datetimen as u8);
1988        assert_eq!(buf[1], 4);
1989        assert_eq!(buf[2], 4);
1990    }
1991
1992    // ----- DateTime2 into a `datetime` (Datetimen) column (lines 774-780) -----
1993
1994    #[cfg(feature = "tds73")]
1995    #[tokio::test]
1996    async fn datetime2_with_varlen_datetimen() {
1997        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None));
1998        // 2020-01-01 is representable by `datetime` (>= 1900-01-01).
1999        let dt2 = DateTime2::new(Date::new(737_425), Time::new(0, 7));
2000        let buf = encode_with_ti(&ti, ColumnData::DateTime2(Some(dt2))).unwrap();
2001
2002        let reader = &mut buf.into_sql_read_bytes();
2003        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2004        assert!(matches!(nd, ColumnData::DateTime(Some(_))), "got {:?}", nd);
2005    }
2006
2007    #[cfg(feature = "tds73")]
2008    #[tokio::test]
2009    async fn none_datetime2_with_varlen_datetimen() {
2010        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None));
2011        let buf = encode_with_ti(&ti, ColumnData::DateTime2(None)).unwrap();
2012
2013        let reader = &mut buf.into_sql_read_bytes();
2014        // Just needs to decode a null cleanly.
2015        ColumnData::decode(reader, &ti).await.unwrap();
2016    }
2017
2018    // ----- Numeric into a Money column (lines 840-847) -----
2019
2020    #[tokio::test]
2021    async fn numeric_with_varlen_money() {
2022        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2023        // Numeric 3.5 (value 35000, scale 4) -> money.
2024        let buf = encode_with_ti(
2025            &ti,
2026            ColumnData::Numeric(Some(Numeric::new_with_scale(35000, 4))),
2027        )
2028        .unwrap();
2029
2030        let reader = &mut buf.into_sql_read_bytes();
2031        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2032        assert_eq!(nd, ColumnData::F64(Some(3.5)));
2033    }
2034
2035    #[tokio::test]
2036    async fn none_numeric_with_varlen_money() {
2037        let ti = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None));
2038        let buf = encode_with_ti(&ti, ColumnData::Numeric(None)).unwrap();
2039
2040        let reader = &mut buf.into_sql_read_bytes();
2041        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2042        assert_eq!(nd, ColumnData::F64(None));
2043    }
2044
2045    // ----- Numeric rescale on encode (lines 859-879) -----
2046
2047    #[tokio::test]
2048    async fn numeric_scaled_up_to_column_scale() {
2049        // Column scale 2 > value scale 0: value 23 -> 2300 at scale 2.
2050        let ti = TypeInfo::VarLenSizedPrecision {
2051            ty: VarLenType::Numericn,
2052            size: 17,
2053            precision: 18,
2054            scale: 2,
2055        };
2056        let buf = encode_with_ti(
2057            &ti,
2058            ColumnData::Numeric(Some(Numeric::new_with_scale(23, 0))),
2059        )
2060        .unwrap();
2061
2062        let reader = &mut buf.into_sql_read_bytes();
2063        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2064        assert_eq!(
2065            nd,
2066            ColumnData::Numeric(Some(Numeric::new_with_scale(2300, 2)))
2067        );
2068    }
2069
2070    #[tokio::test]
2071    async fn numeric_scaled_down_to_column_scale() {
2072        // Column scale 2 < value scale 4: 1.2345 -> 1.23 (rounded half away).
2073        let ti = TypeInfo::VarLenSizedPrecision {
2074            ty: VarLenType::Numericn,
2075            size: 17,
2076            precision: 18,
2077            scale: 2,
2078        };
2079        let buf = encode_with_ti(
2080            &ti,
2081            ColumnData::Numeric(Some(Numeric::new_with_scale(12345, 4))),
2082        )
2083        .unwrap();
2084
2085        let reader = &mut buf.into_sql_read_bytes();
2086        let nd = ColumnData::decode(reader, &ti).await.unwrap();
2087        assert_eq!(
2088            nd,
2089            ColumnData::Numeric(Some(Numeric::new_with_scale(123, 2)))
2090        );
2091    }
2092
2093    // ----- SQL_VARIANT encode (lines 897-898) -----
2094
2095    #[tokio::test]
2096    async fn ssvariant_round_trip_i32() {
2097        test_round_trip(
2098            TypeInfo::VarLenSized(VarLenContext::new(VarLenType::SSVariant, 0, None)),
2099            ColumnData::I32(Some(42)),
2100        )
2101        .await;
2102    }
2103}