Skip to main content

tiberius/tds/codec/token/
token_col_metadata.rs

1use std::{
2    borrow::{BorrowMut, Cow},
3    fmt::Display,
4};
5
6use crate::{
7    tds::codec::{Encode, FixedLenType, TokenType, TypeInfo, VarLenType},
8    Column, ColumnData, ColumnType, SqlReadBytes,
9};
10use asynchronous_codec::BytesMut;
11use bytes::BufMut;
12use enumflags2::{bitflags, BitFlags};
13
14#[derive(Debug, Clone)]
15pub struct TokenColMetaData<'a> {
16    pub columns: Vec<MetaDataColumn<'a>>,
17}
18
19/// Metadata for a single result/table column: its name plus the
20/// [`BaseMetaDataColumn`] describing its type, size and flags.
21#[derive(Debug, Clone)]
22pub struct MetaDataColumn<'a> {
23    /// The type and flag metadata for the column.
24    pub base: BaseMetaDataColumn,
25    /// The name of the column.
26    pub col_name: Cow<'a, str>,
27}
28
29impl<'a> MetaDataColumn<'a> {
30    /// The name of the column.
31    pub fn col_name(&self) -> &str {
32        self.col_name.as_ref()
33    }
34
35    /// The [`BaseMetaDataColumn`] describing the column's type and flags
36    /// (nullability, identity, etc.).
37    pub fn base(&self) -> &BaseMetaDataColumn {
38        &self.base
39    }
40}
41
42impl<'a> Display for MetaDataColumn<'a> {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "[{}] ", self.col_name)?;
45
46        match &self.base.ty {
47            TypeInfo::FixedLen(fixed) => match fixed {
48                FixedLenType::Int1 => write!(f, "tinyint")?,
49                FixedLenType::Bit => write!(f, "bit")?,
50                FixedLenType::Int2 => write!(f, "smallint")?,
51                FixedLenType::Int4 => write!(f, "int")?,
52                FixedLenType::Datetime4 => write!(f, "smalldatetime")?,
53                FixedLenType::Float4 => write!(f, "real")?,
54                FixedLenType::Money => write!(f, "money")?,
55                FixedLenType::Datetime => write!(f, "datetime")?,
56                FixedLenType::Float8 => write!(f, "float")?,
57                FixedLenType::Money4 => write!(f, "smallmoney")?,
58                FixedLenType::Int8 => write!(f, "bigint")?,
59                // The TDS "null" fixed type carries no value; surface it as the
60                // int it decodes to rather than panicking (a bare `SELECT NULL`
61                // produces such a column).
62                FixedLenType::Null => write!(f, "int")?,
63            },
64            TypeInfo::VarLenSized(ctx) => match ctx.r#type() {
65                VarLenType::Bitn => write!(f, "bit")?,
66                VarLenType::Guid => write!(f, "uniqueidentifier")?,
67                #[cfg(feature = "tds73")]
68                VarLenType::Daten => write!(f, "date")?,
69                #[cfg(feature = "tds73")]
70                VarLenType::Timen => write!(f, "time")?,
71                #[cfg(feature = "tds73")]
72                VarLenType::Datetime2 => write!(f, "datetime2({})", ctx.len())?,
73                VarLenType::Datetimen => write!(f, "datetime")?,
74                VarLenType::Money => match ctx.len() {
75                    4 => write!(f, "smallmoney")?,
76                    _ => write!(f, "money")?,
77                },
78                #[cfg(feature = "tds73")]
79                VarLenType::DatetimeOffsetn => write!(f, "datetimeoffset")?,
80                VarLenType::BigVarBin => {
81                    if ctx.len() <= 8000 {
82                        write!(f, "varbinary({})", ctx.len())?
83                    } else {
84                        write!(f, "varbinary(max)")?
85                    }
86                }
87                VarLenType::BigVarChar => {
88                    if ctx.len() <= 8000 {
89                        write!(f, "varchar({})", ctx.len())?
90                    } else {
91                        write!(f, "varchar(max)")?
92                    }
93                }
94                VarLenType::BigBinary => write!(f, "binary({})", ctx.len())?,
95                VarLenType::BigChar => write!(f, "char({})", ctx.len())?,
96                VarLenType::NVarchar => {
97                    if ctx.len() <= 4000 {
98                        write!(f, "nvarchar({})", ctx.len())?
99                    } else {
100                        write!(f, "nvarchar(max)")?
101                    }
102                }
103                VarLenType::NChar => write!(f, "nchar({})", ctx.len())?,
104                VarLenType::Text => write!(f, "text")?,
105                VarLenType::Image => write!(f, "image")?,
106                VarLenType::NText => write!(f, "ntext")?,
107                VarLenType::Intn => match ctx.len() {
108                    1 => write!(f, "tinyint")?,
109                    2 => write!(f, "smallint")?,
110                    4 => write!(f, "int")?,
111                    _ => write!(f, "bigint")?,
112                },
113                VarLenType::Floatn => match ctx.len() {
114                    4 => write!(f, "real")?,
115                    _ => write!(f, "float")?,
116                },
117                VarLenType::SSVariant => write!(f, "sql_variant")?,
118                // Any other var-len type: emit its debug name rather than
119                // panicking, so formatting metadata never crashes.
120                other => write!(f, "{other:?}")?,
121            },
122            TypeInfo::VarLenSizedPrecision {
123                ty,
124                size: _,
125                precision,
126                scale,
127            } => match ty {
128                VarLenType::Numericn => write!(f, "numeric({},{})", precision, scale)?,
129                // Decimaln, and any other precision-carrying type.
130                _ => write!(f, "decimal({},{})", precision, scale)?,
131            },
132            TypeInfo::Xml { .. } => write!(f, "xml")?,
133            TypeInfo::Udt(info) => write!(f, "{}.{}", info.schema_name, info.type_name)?,
134        }
135
136        Ok(())
137    }
138}
139
140/// Describes the type and flags of a column, exposing metadata such as the
141/// column type (including size, precision and scale), whether the column is
142/// nullable and whether it is an identity column.
143#[derive(Debug, Clone)]
144pub struct BaseMetaDataColumn {
145    /// The set of [`ColumnFlag`]s describing the column (nullability, identity,
146    /// updateability, and so on).
147    pub flags: BitFlags<ColumnFlag>,
148    /// The type of the column, including its size, precision and scale where
149    /// applicable.
150    pub ty: TypeInfo,
151}
152
153impl BaseMetaDataColumn {
154    /// The type of the column, including its size, precision and scale where
155    /// applicable.
156    pub fn ty(&self) -> &TypeInfo {
157        &self.ty
158    }
159
160    /// The set of flags describing the column.
161    pub fn flags(&self) -> BitFlags<ColumnFlag> {
162        self.flags
163    }
164
165    /// `true` if the column accepts `NULL` values.
166    pub fn is_nullable(&self) -> bool {
167        self.flags.contains(ColumnFlag::Nullable)
168    }
169
170    /// `true` if the column is an identity column.
171    pub fn is_identity(&self) -> bool {
172        self.flags.contains(ColumnFlag::Identity)
173    }
174
175    /// `true` if the column is writeable (e.g. usable as a bulk-insert target).
176    pub fn is_updateable(&self) -> bool {
177        self.flags.contains(ColumnFlag::Updateable)
178    }
179
180    pub(crate) fn null_value(&self) -> ColumnData<'static> {
181        match &self.ty {
182            TypeInfo::FixedLen(ty) => match ty {
183                FixedLenType::Null => ColumnData::I32(None),
184                FixedLenType::Int1 => ColumnData::U8(None),
185                FixedLenType::Bit => ColumnData::Bit(None),
186                FixedLenType::Int2 => ColumnData::I16(None),
187                FixedLenType::Int4 => ColumnData::I32(None),
188                FixedLenType::Datetime4 => ColumnData::SmallDateTime(None),
189                FixedLenType::Float4 => ColumnData::F32(None),
190                FixedLenType::Money => ColumnData::F64(None),
191                FixedLenType::Datetime => ColumnData::DateTime(None),
192                FixedLenType::Float8 => ColumnData::F64(None),
193                FixedLenType::Money4 => ColumnData::F32(None),
194                FixedLenType::Int8 => ColumnData::I64(None),
195            },
196            TypeInfo::VarLenSized(cx) => match cx.r#type() {
197                VarLenType::Guid => ColumnData::Guid(None),
198                VarLenType::Intn => match cx.len() {
199                    1 => ColumnData::U8(None),
200                    2 => ColumnData::I16(None),
201                    4 => ColumnData::I32(None),
202                    _ => ColumnData::I64(None),
203                },
204                VarLenType::Bitn => ColumnData::Bit(None),
205                VarLenType::Decimaln => ColumnData::Numeric(None),
206                VarLenType::Numericn => ColumnData::Numeric(None),
207                VarLenType::Floatn => match cx.len() {
208                    4 => ColumnData::F32(None),
209                    _ => ColumnData::F64(None),
210                },
211                VarLenType::Money => ColumnData::F64(None),
212                VarLenType::Datetimen => ColumnData::DateTime(None),
213                #[cfg(feature = "tds73")]
214                VarLenType::Daten => ColumnData::Date(None),
215                #[cfg(feature = "tds73")]
216                VarLenType::Timen => ColumnData::Time(None),
217                #[cfg(feature = "tds73")]
218                VarLenType::Datetime2 => ColumnData::DateTime2(None),
219                #[cfg(feature = "tds73")]
220                VarLenType::DatetimeOffsetn => ColumnData::DateTimeOffset(None),
221                VarLenType::BigVarBin => ColumnData::Binary(None),
222                VarLenType::BigVarChar => ColumnData::String(None),
223                VarLenType::BigBinary => ColumnData::Binary(None),
224                VarLenType::BigChar => ColumnData::String(None),
225                VarLenType::NVarchar => ColumnData::String(None),
226                VarLenType::NChar => ColumnData::String(None),
227                VarLenType::Xml => ColumnData::Xml(None),
228                // A null CLR UDT carries no payload; surface it as a null
229                // binary, matching `udt::decode` (which yields
230                // `ColumnData::Binary`). Previously this panicked via `todo!()`,
231                // which a bulk insert of a NULL UDT column could reach.
232                VarLenType::Udt => ColumnData::Binary(None),
233                VarLenType::Text => ColumnData::String(None),
234                VarLenType::Image => ColumnData::Binary(None),
235                VarLenType::NText => ColumnData::String(None),
236                // A null `sql_variant` carries no base type, so surface a
237                // generic null value.
238                VarLenType::SSVariant => ColumnData::String(None),
239            },
240            TypeInfo::VarLenSizedPrecision { ty, .. } => match ty {
241                VarLenType::Guid => ColumnData::Guid(None),
242                VarLenType::Intn => ColumnData::I32(None),
243                VarLenType::Bitn => ColumnData::Bit(None),
244                VarLenType::Decimaln => ColumnData::Numeric(None),
245                VarLenType::Numericn => ColumnData::Numeric(None),
246                VarLenType::Floatn => ColumnData::F32(None),
247                VarLenType::Money => ColumnData::F64(None),
248                VarLenType::Datetimen => ColumnData::DateTime(None),
249                #[cfg(feature = "tds73")]
250                VarLenType::Daten => ColumnData::Date(None),
251                #[cfg(feature = "tds73")]
252                VarLenType::Timen => ColumnData::Time(None),
253                #[cfg(feature = "tds73")]
254                VarLenType::Datetime2 => ColumnData::DateTime2(None),
255                #[cfg(feature = "tds73")]
256                VarLenType::DatetimeOffsetn => ColumnData::DateTimeOffset(None),
257                VarLenType::BigVarBin => ColumnData::Binary(None),
258                VarLenType::BigVarChar => ColumnData::String(None),
259                VarLenType::BigBinary => ColumnData::Binary(None),
260                VarLenType::BigChar => ColumnData::String(None),
261                VarLenType::NVarchar => ColumnData::String(None),
262                VarLenType::NChar => ColumnData::String(None),
263                VarLenType::Xml => ColumnData::Xml(None),
264                // A null CLR UDT carries no payload; surface it as a null
265                // binary, matching `udt::decode` (which yields
266                // `ColumnData::Binary`). Previously this panicked via `todo!()`,
267                // which a bulk insert of a NULL UDT column could reach.
268                VarLenType::Udt => ColumnData::Binary(None),
269                VarLenType::Text => ColumnData::String(None),
270                VarLenType::Image => ColumnData::Binary(None),
271                VarLenType::NText => ColumnData::String(None),
272                // A null `sql_variant` carries no base type, so surface a
273                // generic null value.
274                VarLenType::SSVariant => ColumnData::String(None),
275            },
276            TypeInfo::Xml { .. } => ColumnData::Xml(None),
277            TypeInfo::Udt(_) => ColumnData::Binary(None),
278        }
279    }
280}
281
282impl<'a> Encode<BytesMut> for TokenColMetaData<'a> {
283    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
284        dst.put_u8(TokenType::ColMetaData as u8);
285        dst.put_u16_le(self.columns.len() as u16);
286
287        for col in self.columns.into_iter() {
288            col.encode(dst)?;
289        }
290
291        Ok(())
292    }
293}
294
295impl<'a> Encode<BytesMut> for MetaDataColumn<'a> {
296    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
297        dst.put_u32_le(0);
298        self.base.encode(dst)?;
299
300        let len_pos = dst.len();
301        let mut length = 0u8;
302
303        dst.put_u8(length);
304
305        for chr in self.col_name.encode_utf16() {
306            length += 1;
307            dst.put_u16_le(chr);
308        }
309
310        let dst: &mut [u8] = dst.borrow_mut();
311        dst[len_pos] = length;
312
313        Ok(())
314    }
315}
316
317impl Encode<BytesMut> for BaseMetaDataColumn {
318    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
319        dst.put_u16_le(BitFlags::bits(self.flags));
320        self.ty.encode(dst)?;
321
322        Ok(())
323    }
324}
325
326/// A setting a column can hold.
327#[bitflags]
328#[repr(u16)]
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330pub enum ColumnFlag {
331    /// The column can be null.
332    Nullable = 1,
333    /// Set for string columns with binary collation and always for the XML data
334    /// type.
335    CaseSensitive = 1 << 1,
336    /// If column is writeable.
337    Updateable = 1 << 3,
338    /// Column modification status unknown.
339    UpdateableUnknown = 1 << 2,
340    /// Column is an identity.
341    Identity = 1 << 4,
342    /// Coulumn is computed.
343    Computed = 1 << 7,
344    /// Column is a fixed-length common language runtime user-defined type (CLR
345    /// UDT).
346    FixedLenClrType = 1 << 10,
347    /// Column is the special XML column for the sparse column set.
348    SparseColumnSet = 1 << 11,
349    /// Column is encrypted transparently and has to be decrypted to view the
350    /// plaintext value. This flag is valid when the column encryption feature
351    /// is negotiated between client and server and is turned on.
352    Encrypted = 1 << 12,
353    /// Column is part of a hidden primary key created to support a T-SQL SELECT
354    /// statement containing FOR BROWSE.
355    Hidden = 1 << 13,
356    /// Column is part of a primary key for the row and the T-SQL SELECT
357    /// statement contains FOR BROWSE.
358    Key = 1 << 14,
359    /// It is unknown whether the column might be nullable.
360    NullableUnknown = 1 << 15,
361}
362
363impl TokenColMetaData<'static> {
364    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
365    where
366        R: SqlReadBytes + Unpin,
367    {
368        let column_count = src.read_u16_le().await?;
369        // `column_count` is an untrusted u16 (up to 65535); cap the up-front
370        // reservation so a hostile COLMETADATA token can't force a large
371        // transient allocation before the column bodies arrive. The Vec still
372        // grows as real columns are decoded.
373        let mut columns = Vec::with_capacity(
374            (column_count as usize).min(crate::tds::codec::column_data::MAX_PREALLOC),
375        );
376
377        // `0xffff` is the "no metadata" sentinel; any other count drives the
378        // loop directly (a count of 0 simply iterates zero times).
379        if column_count < 0xffff {
380            for _ in 0..column_count {
381                let base = BaseMetaDataColumn::decode(src).await?;
382                let col_name = Cow::from(src.read_b_varchar().await?);
383
384                columns.push(MetaDataColumn { base, col_name });
385            }
386        }
387
388        Ok(TokenColMetaData { columns })
389    }
390}
391
392impl<'a> TokenColMetaData<'a> {
393    pub(crate) fn columns(&self) -> impl Iterator<Item = Column> + '_ {
394        self.columns.iter().map(|x| Column {
395            name: x.col_name.to_string(),
396            column_type: ColumnType::from(&x.base.ty),
397        })
398    }
399}
400
401impl BaseMetaDataColumn {
402    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
403    where
404        R: SqlReadBytes + Unpin,
405    {
406        use VarLenType::*;
407
408        let _user_ty = src.read_u32_le().await?;
409
410        // The COLMETADATA `Flags` field (MS-TDS §2.2.7.4) is a 16-bit field that
411        // includes reserved / ODBC bits the server may set and which future
412        // protocol revisions may extend. Truncate to the flags we model rather
413        // than rejecting the whole token on an unrecognized bit.
414        let flags = BitFlags::from_bits_truncate(src.read_u16_le().await?);
415
416        let ty = TypeInfo::decode(src).await?;
417
418        if let TypeInfo::VarLenSized(cx) = ty {
419            if let Text | NText | Image = cx.r#type() {
420                let num_of_parts = src.read_u8().await?;
421
422                // table name
423                for _ in 0..num_of_parts {
424                    src.read_us_varchar().await?;
425                }
426            };
427        };
428
429        Ok(BaseMetaDataColumn { flags, ty })
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
437    use crate::tds::Collation;
438    use crate::VarLenContext;
439
440    fn meta(ty: TypeInfo, name: &'static str) -> MetaDataColumn<'static> {
441        MetaDataColumn {
442            base: BaseMetaDataColumn {
443                flags: ColumnFlag::Nullable.into(),
444                ty,
445            },
446            col_name: Cow::Borrowed(name),
447        }
448    }
449
450    #[tokio::test]
451    async fn round_trip_via_encode_decode() {
452        let cmd = TokenColMetaData {
453            columns: vec![
454                meta(TypeInfo::FixedLen(FixedLenType::Int4), "id"),
455                meta(
456                    TypeInfo::VarLenSized(VarLenContext::new(
457                        VarLenType::NVarchar,
458                        4000,
459                        Some(Collation::new(13632521, 52)),
460                    )),
461                    "name",
462                ),
463            ],
464        };
465
466        // Build a decodable buffer: column count followed by each column. The
467        // MetaDataColumn encoder writes the leading user-type u32 that the
468        // decoder expects.
469        let mut buf = BytesMut::new();
470        buf.put_u16_le(cmd.columns.len() as u16);
471        for col in cmd.columns.iter().cloned() {
472            col.encode(&mut buf).unwrap();
473        }
474
475        let decoded = TokenColMetaData::decode(&mut buf.into_sql_read_bytes())
476            .await
477            .unwrap();
478
479        assert_eq!(decoded.columns.len(), 2);
480        assert_eq!(decoded.columns[0].col_name, "id");
481        assert_eq!(decoded.columns[1].col_name, "name");
482
483        let columns: Vec<_> = decoded.columns().collect();
484        assert_eq!(columns.len(), 2);
485        assert_eq!(columns[0].name(), "id");
486    }
487
488    #[test]
489    fn encode_writes_token_header_and_column_count() {
490        let cmd = TokenColMetaData {
491            columns: vec![
492                meta(TypeInfo::FixedLen(FixedLenType::Int4), "id"),
493                meta(TypeInfo::FixedLen(FixedLenType::Bit), "flag"),
494            ],
495        };
496
497        let mut buf = BytesMut::new();
498        cmd.encode(&mut buf).unwrap();
499
500        // First the ColMetaData token byte, then the little-endian column count.
501        assert_eq!(buf[0], TokenType::ColMetaData as u8);
502        assert_eq!(u16::from_le_bytes([buf[1], buf[2]]), 2);
503        // The two column bodies follow the 3-byte header.
504        assert!(buf.len() > 3);
505    }
506
507    #[tokio::test]
508    async fn zero_columns_yields_empty() {
509        let mut buf = BytesMut::new();
510        buf.put_u16_le(0);
511
512        let decoded = TokenColMetaData::decode(&mut buf.into_sql_read_bytes())
513            .await
514            .unwrap();
515        assert!(decoded.columns.is_empty());
516    }
517
518    #[tokio::test]
519    async fn text_column_reads_table_name_parts() {
520        let mut buf = BytesMut::new();
521        buf.put_u16_le(1); // one column
522
523        // user_ty + flags
524        buf.put_u32_le(0);
525        buf.put_u16_le(BitFlags::bits(BitFlags::from(ColumnFlag::Nullable)));
526
527        // type info for a text column with collation
528        let ti = TypeInfo::VarLenSized(VarLenContext::new(
529            VarLenType::Text,
530            2147483647,
531            Some(Collation::new(13632521, 52)),
532        ));
533        ti.encode(&mut buf).unwrap();
534
535        // table name: one part, us_varchar "dbo"
536        buf.put_u8(1);
537        let part: Vec<u16> = "dbo".encode_utf16().collect();
538        buf.put_u16_le(part.len() as u16);
539        for c in part {
540            buf.put_u16_le(c);
541        }
542
543        // column name (b_varchar)
544        let name: Vec<u16> = "body".encode_utf16().collect();
545        buf.put_u8(name.len() as u8);
546        for c in name {
547            buf.put_u16_le(c);
548        }
549
550        let decoded = TokenColMetaData::decode(&mut buf.into_sql_read_bytes())
551            .await
552            .unwrap();
553
554        assert_eq!(decoded.columns.len(), 1);
555        assert_eq!(decoded.columns[0].col_name, "body");
556    }
557
558    #[test]
559    fn display_formats_various_types() {
560        let cases = vec![
561            (TypeInfo::FixedLen(FixedLenType::Int4), "c int"),
562            (TypeInfo::FixedLen(FixedLenType::Bit), "c bit"),
563            (TypeInfo::FixedLen(FixedLenType::Float8), "c float"),
564            (
565                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
566                "c tinyint",
567            ),
568            (
569                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
570                "c int",
571            ),
572            (
573                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
574                "c real",
575            ),
576            (
577                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
578                "c uniqueidentifier",
579            ),
580            (
581                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 100, None)),
582                "c varbinary(100)",
583            ),
584            (
585                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarBin, 100000, None)),
586                "c varbinary(max)",
587            ),
588            (
589                TypeInfo::VarLenSizedPrecision {
590                    ty: VarLenType::Decimaln,
591                    size: 17,
592                    precision: 18,
593                    scale: 2,
594                },
595                "c decimal(18,2)",
596            ),
597            // Numericn must render as `numeric(...)`, distinct from the
598            // `decimal(...)` fallback that every other precision type uses.
599            (
600                TypeInfo::VarLenSizedPrecision {
601                    ty: VarLenType::Numericn,
602                    size: 9,
603                    precision: 10,
604                    scale: 4,
605                },
606                "c numeric(10,4)",
607            ),
608            (
609                TypeInfo::Xml {
610                    schema: None,
611                    size: 0,
612                },
613                "c xml",
614            ),
615        ];
616
617        for (ty, expected) in cases {
618            // Display brackets the column name for use in bulk `INSERT` statements.
619            let expected = expected.replacen("c ", "[c] ", 1);
620            assert_eq!(format!("{}", meta(ty, "c")), expected);
621        }
622    }
623
624    #[test]
625    fn null_value_maps_types() {
626        let fixed = BaseMetaDataColumn {
627            flags: BitFlags::empty(),
628            ty: TypeInfo::FixedLen(FixedLenType::Int4),
629        };
630        assert_eq!(fixed.null_value(), ColumnData::I32(None));
631
632        let varlen = BaseMetaDataColumn {
633            flags: BitFlags::empty(),
634            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
635        };
636        assert_eq!(varlen.null_value(), ColumnData::I16(None));
637
638        // Each Intn width maps to a distinct integer column; 1 and 4 sit either
639        // side of the `_ => I64` fallback and pin their own arms.
640        let tinyint = BaseMetaDataColumn {
641            flags: BitFlags::empty(),
642            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 1, None)),
643        };
644        assert_eq!(tinyint.null_value(), ColumnData::U8(None));
645
646        let int4 = BaseMetaDataColumn {
647            flags: BitFlags::empty(),
648            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 4, None)),
649        };
650        assert_eq!(int4.null_value(), ColumnData::I32(None));
651
652        let int8 = BaseMetaDataColumn {
653            flags: BitFlags::empty(),
654            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
655        };
656        assert_eq!(int8.null_value(), ColumnData::I64(None));
657
658        // Floatn splits on width too: 4 bytes is F32, anything else F64.
659        let real = BaseMetaDataColumn {
660            flags: BitFlags::empty(),
661            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 4, None)),
662        };
663        assert_eq!(real.null_value(), ColumnData::F32(None));
664
665        let double = BaseMetaDataColumn {
666            flags: BitFlags::empty(),
667            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
668        };
669        assert_eq!(double.null_value(), ColumnData::F64(None));
670
671        let guid = BaseMetaDataColumn {
672            flags: BitFlags::empty(),
673            ty: TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Guid, 16, None)),
674        };
675        assert_eq!(guid.null_value(), ColumnData::Guid(None));
676    }
677
678    #[test]
679    fn null_value_maps_precision_and_xml_and_udt() {
680        let precision = BaseMetaDataColumn {
681            flags: BitFlags::empty(),
682            ty: TypeInfo::VarLenSizedPrecision {
683                ty: VarLenType::Numericn,
684                size: 17,
685                precision: 18,
686                scale: 2,
687            },
688        };
689        assert_eq!(precision.null_value(), ColumnData::Numeric(None));
690
691        let xml = BaseMetaDataColumn {
692            flags: BitFlags::empty(),
693            ty: TypeInfo::Xml {
694                schema: None,
695                size: 0,
696            },
697        };
698        assert_eq!(xml.null_value(), ColumnData::Xml(None));
699
700        let udt = BaseMetaDataColumn {
701            flags: BitFlags::empty(),
702            ty: TypeInfo::Udt(crate::tds::codec::type_info::UdtInfo {
703                max_byte_size: 0,
704                db_name: "db".into(),
705                schema_name: "dbo".into(),
706                type_name: "T".into(),
707                assembly_qualified_name: "A".into(),
708            }),
709        };
710        assert_eq!(udt.null_value(), ColumnData::Binary(None));
711    }
712
713    #[test]
714    fn base_meta_data_column_flag_accessors() {
715        let base = BaseMetaDataColumn {
716            flags: ColumnFlag::Nullable | ColumnFlag::Identity | ColumnFlag::Updateable,
717            ty: TypeInfo::FixedLen(FixedLenType::Int4),
718        };
719
720        assert!(base.is_nullable());
721        assert!(base.is_identity());
722        assert!(base.is_updateable());
723        assert_eq!(base.ty(), &TypeInfo::FixedLen(FixedLenType::Int4));
724        assert_eq!(base.flags(), base.flags);
725
726        let base2 = BaseMetaDataColumn {
727            flags: BitFlags::empty(),
728            ty: TypeInfo::FixedLen(FixedLenType::Int4),
729        };
730        assert!(!base2.is_nullable());
731        assert!(!base2.is_identity());
732        assert!(!base2.is_updateable());
733    }
734
735    #[test]
736    fn meta_data_column_accessors() {
737        let m = meta(TypeInfo::FixedLen(FixedLenType::Int4), "id");
738        assert_eq!(m.col_name(), "id");
739        assert!(m.base().is_nullable());
740    }
741
742    #[test]
743    fn display_formats_more_types() {
744        let cases = vec![
745            (
746                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Bitn, 1, None)),
747                "c bit",
748            ),
749            (
750                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetimen, 8, None)),
751                "c datetime",
752            ),
753            (
754                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 4, None)),
755                "c smallmoney",
756            ),
757            (
758                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Money, 8, None)),
759                "c money",
760            ),
761            (
762                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarChar, 100, None)),
763                "c varchar(100)",
764            ),
765            (
766                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigVarChar, 100000, None)),
767                "c varchar(max)",
768            ),
769            (
770                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigBinary, 10, None)),
771                "c binary(10)",
772            ),
773            (
774                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::BigChar, 10, None)),
775                "c char(10)",
776            ),
777            (
778                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NVarchar, 100, None)),
779                "c nvarchar(100)",
780            ),
781            (
782                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NVarchar, 100000, None)),
783                "c nvarchar(max)",
784            ),
785            (
786                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NChar, 10, None)),
787                "c nchar(10)",
788            ),
789            (
790                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Text, 0, None)),
791                "c text",
792            ),
793            (
794                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Image, 0, None)),
795                "c image",
796            ),
797            (
798                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::NText, 0, None)),
799                "c ntext",
800            ),
801            (
802                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 2, None)),
803                "c smallint",
804            ),
805            (
806                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Intn, 8, None)),
807                "c bigint",
808            ),
809            (
810                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Floatn, 8, None)),
811                "c float",
812            ),
813            (
814                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::SSVariant, 0, None)),
815                "c sql_variant",
816            ),
817        ];
818
819        for (ty, expected) in cases {
820            let expected = expected.replacen("c ", "[c] ", 1);
821            assert_eq!(format!("{}", meta(ty, "c")), expected);
822        }
823    }
824
825    #[test]
826    fn display_formats_udt_and_decimaln() {
827        let udt = TypeInfo::Udt(crate::tds::codec::type_info::UdtInfo {
828            max_byte_size: 0,
829            db_name: "db".into(),
830            schema_name: "dbo".into(),
831            type_name: "MyType".into(),
832            assembly_qualified_name: "asm".into(),
833        });
834        assert_eq!(format!("{}", meta(udt, "c")), "[c] dbo.MyType");
835
836        let decimaln = TypeInfo::VarLenSizedPrecision {
837            ty: VarLenType::Decimaln,
838            size: 17,
839            precision: 10,
840            scale: 4,
841        };
842        assert_eq!(format!("{}", meta(decimaln, "c")), "[c] decimal(10,4)");
843    }
844
845    #[tokio::test]
846    async fn decode_all_ones_column_count_yields_empty() {
847        // column_count == 0xffff is treated as "no columns" (guards against a
848        // sentinel/placeholder value rather than a real column list).
849        let mut buf = BytesMut::new();
850        buf.put_u16_le(0xffff);
851
852        let decoded = TokenColMetaData::decode(&mut buf.into_sql_read_bytes())
853            .await
854            .unwrap();
855        assert!(decoded.columns.is_empty());
856    }
857
858    #[test]
859    fn display_formats_fixed_len_money_and_datetime_types() {
860        // Covers the FixedLenType Display arms not exercised elsewhere:
861        // tinyint/smallint/smalldatetime/real/money/datetime/smallmoney/bigint
862        // and the `Null` sentinel (which surfaces as `int`).
863        let cases = vec![
864            (TypeInfo::FixedLen(FixedLenType::Int1), "c tinyint"),
865            (TypeInfo::FixedLen(FixedLenType::Int2), "c smallint"),
866            (
867                TypeInfo::FixedLen(FixedLenType::Datetime4),
868                "c smalldatetime",
869            ),
870            (TypeInfo::FixedLen(FixedLenType::Float4), "c real"),
871            (TypeInfo::FixedLen(FixedLenType::Money), "c money"),
872            (TypeInfo::FixedLen(FixedLenType::Datetime), "c datetime"),
873            (TypeInfo::FixedLen(FixedLenType::Money4), "c smallmoney"),
874            (TypeInfo::FixedLen(FixedLenType::Int8), "c bigint"),
875            (TypeInfo::FixedLen(FixedLenType::Null), "c int"),
876        ];
877
878        for (ty, expected) in cases {
879            let expected = expected.replacen("c ", "[c] ", 1);
880            assert_eq!(format!("{}", meta(ty, "c")), expected);
881        }
882    }
883
884    #[cfg(feature = "tds73")]
885    #[test]
886    fn display_formats_tds73_date_time_types() {
887        // date/time/datetime2/datetimeoffset Display arms (tds73-only).
888        let cases = vec![
889            (
890                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Daten, 3, None)),
891                "c date",
892            ),
893            (
894                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Timen, 7, None)),
895                "c time",
896            ),
897            (
898                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Datetime2, 7, None)),
899                "c datetime2(7)",
900            ),
901            (
902                TypeInfo::VarLenSized(VarLenContext::new(VarLenType::DatetimeOffsetn, 7, None)),
903                "c datetimeoffset",
904            ),
905        ];
906
907        for (ty, expected) in cases {
908            let expected = expected.replacen("c ", "[c] ", 1);
909            assert_eq!(format!("{}", meta(ty, "c")), expected);
910        }
911    }
912
913    #[test]
914    fn display_var_len_other_fallback_uses_debug_name() {
915        // A VarLenSized carrying a type not matched by any explicit Display arm
916        // (e.g. Decimaln) hits the `other => {other:?}` fallback rather than
917        // panicking.
918        let ty = TypeInfo::VarLenSized(VarLenContext::new(VarLenType::Decimaln, 17, None));
919        assert_eq!(format!("{}", meta(ty, "c")), "[c] Decimaln");
920    }
921
922    #[test]
923    fn null_value_all_fixed_len() {
924        use FixedLenType::*;
925        let cases = [
926            (Null, ColumnData::I32(None)),
927            (Int1, ColumnData::U8(None)),
928            (Bit, ColumnData::Bit(None)),
929            (Int2, ColumnData::I16(None)),
930            (Int4, ColumnData::I32(None)),
931            (Datetime4, ColumnData::SmallDateTime(None)),
932            (Float4, ColumnData::F32(None)),
933            (Money, ColumnData::F64(None)),
934            (Datetime, ColumnData::DateTime(None)),
935            (Float8, ColumnData::F64(None)),
936            (Money4, ColumnData::F32(None)),
937            (Int8, ColumnData::I64(None)),
938        ];
939
940        for (ty, expected) in cases {
941            let base = BaseMetaDataColumn {
942                flags: BitFlags::empty(),
943                ty: TypeInfo::FixedLen(ty),
944            };
945            assert_eq!(base.null_value(), expected);
946        }
947    }
948
949    fn vsize_null(ty: VarLenType, len: usize) -> ColumnData<'static> {
950        BaseMetaDataColumn {
951            flags: BitFlags::empty(),
952            ty: TypeInfo::VarLenSized(VarLenContext::new(ty, len, None)),
953        }
954        .null_value()
955    }
956
957    #[test]
958    fn null_value_all_var_len_sized() {
959        use VarLenType::*;
960        assert_eq!(vsize_null(Guid, 16), ColumnData::Guid(None));
961        assert_eq!(vsize_null(Bitn, 1), ColumnData::Bit(None));
962        assert_eq!(vsize_null(Decimaln, 17), ColumnData::Numeric(None));
963        assert_eq!(vsize_null(Numericn, 17), ColumnData::Numeric(None));
964        assert_eq!(vsize_null(Money, 8), ColumnData::F64(None));
965        assert_eq!(vsize_null(Datetimen, 8), ColumnData::DateTime(None));
966        assert_eq!(vsize_null(BigVarBin, 100), ColumnData::Binary(None));
967        assert_eq!(vsize_null(BigVarChar, 100), ColumnData::String(None));
968        assert_eq!(vsize_null(BigBinary, 10), ColumnData::Binary(None));
969        assert_eq!(vsize_null(BigChar, 10), ColumnData::String(None));
970        assert_eq!(vsize_null(NVarchar, 100), ColumnData::String(None));
971        assert_eq!(vsize_null(NChar, 10), ColumnData::String(None));
972        assert_eq!(vsize_null(Xml, 0), ColumnData::Xml(None));
973        assert_eq!(vsize_null(Udt, 0), ColumnData::Binary(None));
974        assert_eq!(vsize_null(Text, 0), ColumnData::String(None));
975        assert_eq!(vsize_null(Image, 0), ColumnData::Binary(None));
976        assert_eq!(vsize_null(NText, 0), ColumnData::String(None));
977        assert_eq!(vsize_null(SSVariant, 0), ColumnData::String(None));
978    }
979
980    #[cfg(feature = "tds73")]
981    #[test]
982    fn null_value_var_len_sized_tds73() {
983        use VarLenType::*;
984        assert_eq!(vsize_null(Daten, 3), ColumnData::Date(None));
985        assert_eq!(vsize_null(Timen, 7), ColumnData::Time(None));
986        assert_eq!(vsize_null(Datetime2, 7), ColumnData::DateTime2(None));
987        assert_eq!(
988            vsize_null(DatetimeOffsetn, 7),
989            ColumnData::DateTimeOffset(None)
990        );
991    }
992
993    fn vprec_null(ty: VarLenType) -> ColumnData<'static> {
994        BaseMetaDataColumn {
995            flags: BitFlags::empty(),
996            ty: TypeInfo::VarLenSizedPrecision {
997                ty,
998                size: 8,
999                precision: 18,
1000                scale: 2,
1001            },
1002        }
1003        .null_value()
1004    }
1005
1006    #[test]
1007    fn null_value_all_var_len_precision() {
1008        use VarLenType::*;
1009        assert_eq!(vprec_null(Guid), ColumnData::Guid(None));
1010        assert_eq!(vprec_null(Intn), ColumnData::I32(None));
1011        assert_eq!(vprec_null(Bitn), ColumnData::Bit(None));
1012        assert_eq!(vprec_null(Decimaln), ColumnData::Numeric(None));
1013        assert_eq!(vprec_null(Numericn), ColumnData::Numeric(None));
1014        assert_eq!(vprec_null(Floatn), ColumnData::F32(None));
1015        assert_eq!(vprec_null(Money), ColumnData::F64(None));
1016        assert_eq!(vprec_null(Datetimen), ColumnData::DateTime(None));
1017        assert_eq!(vprec_null(BigVarBin), ColumnData::Binary(None));
1018        assert_eq!(vprec_null(BigVarChar), ColumnData::String(None));
1019        assert_eq!(vprec_null(BigBinary), ColumnData::Binary(None));
1020        assert_eq!(vprec_null(BigChar), ColumnData::String(None));
1021        assert_eq!(vprec_null(NVarchar), ColumnData::String(None));
1022        assert_eq!(vprec_null(NChar), ColumnData::String(None));
1023        assert_eq!(vprec_null(Xml), ColumnData::Xml(None));
1024        assert_eq!(vprec_null(Udt), ColumnData::Binary(None));
1025        assert_eq!(vprec_null(Text), ColumnData::String(None));
1026        assert_eq!(vprec_null(Image), ColumnData::Binary(None));
1027        assert_eq!(vprec_null(NText), ColumnData::String(None));
1028        assert_eq!(vprec_null(SSVariant), ColumnData::String(None));
1029    }
1030
1031    #[cfg(feature = "tds73")]
1032    #[test]
1033    fn null_value_var_len_precision_tds73() {
1034        use VarLenType::*;
1035        assert_eq!(vprec_null(Daten), ColumnData::Date(None));
1036        assert_eq!(vprec_null(Timen), ColumnData::Time(None));
1037        assert_eq!(vprec_null(Datetime2), ColumnData::DateTime2(None));
1038        assert_eq!(
1039            vprec_null(DatetimeOffsetn),
1040            ColumnData::DateTimeOffset(None)
1041        );
1042    }
1043
1044    #[test]
1045    fn column_flag_bits_are_distinct() {
1046        let all = ColumnFlag::Nullable
1047            | ColumnFlag::CaseSensitive
1048            | ColumnFlag::Updateable
1049            | ColumnFlag::UpdateableUnknown
1050            | ColumnFlag::Identity
1051            | ColumnFlag::Computed
1052            | ColumnFlag::FixedLenClrType
1053            | ColumnFlag::SparseColumnSet
1054            | ColumnFlag::Encrypted
1055            | ColumnFlag::Hidden
1056            | ColumnFlag::Key
1057            | ColumnFlag::NullableUnknown;
1058
1059        assert!(all.contains(ColumnFlag::Nullable));
1060        assert!(all.contains(ColumnFlag::NullableUnknown));
1061        assert_eq!(BitFlags::bits(all).count_ones(), 12);
1062    }
1063}