Skip to main content

tds_driver/
row.rs

1use crate::{
2    error::Error,
3    tds::codec::{ColumnData, FixedLenType, TokenRow, TypeInfo, VarLenType},
4    FromSql,
5};
6use std::{fmt::Display, sync::Arc};
7
8/// A column of data from a query.
9#[derive(Debug, Clone)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub struct Column {
12    pub(crate) name: String,
13    pub(crate) column_type: ColumnType,
14}
15
16impl Column {
17    /// Construct a new Column.
18    pub fn new(name: String, column_type: ColumnType) -> Self {
19        Self { name, column_type }
20    }
21
22    /// The name of the column.
23    pub fn name(&self) -> &str {
24        &self.name
25    }
26
27    /// The type of the column.
28    pub fn column_type(&self) -> ColumnType {
29        self.column_type
30    }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35/// The type of the column.
36pub enum ColumnType {
37    /// The column doesn't have a specified type.
38    Null,
39    /// A bit or boolean value.
40    Bit,
41    /// An 8-bit integer value.
42    Int1,
43    /// A 16-bit integer value.
44    Int2,
45    /// A 32-bit integer value.
46    Int4,
47    /// A 64-bit integer value.
48    Int8,
49    /// A 32-bit datetime value.
50    Datetime4,
51    /// A 32-bit floating point value.
52    Float4,
53    /// A 64-bit floating point value.
54    Float8,
55    /// Money value.
56    Money,
57    /// A TDS 7.2 datetime value.
58    Datetime,
59    /// A 32-bit money value.
60    Money4,
61    /// A unique identifier, UUID.
62    Guid,
63    /// N-bit integer value (variable).
64    Intn,
65    /// A bit value in a variable-length type.
66    Bitn,
67    /// A decimal value (same as `Numericn`).
68    Decimaln,
69    /// A numeric value (same as `Decimaln`).
70    Numericn,
71    /// A n-bit floating point value.
72    Floatn,
73    /// A n-bit datetime value (TDS 7.2).
74    Datetimen,
75    /// A n-bit date value (TDS 7.3).
76    Daten,
77    /// A n-bit time value (TDS 7.3).
78    Timen,
79    /// A n-bit datetime2 value (TDS 7.3).
80    Datetime2,
81    /// A n-bit datetime value with an offset (TDS 7.3).
82    DatetimeOffsetn,
83    /// A variable binary value.
84    BigVarBin,
85    /// A large variable string value.
86    BigVarChar,
87    /// A binary value.
88    BigBinary,
89    /// A string value.
90    BigChar,
91    /// A variable string value with UTF-16 encoding.
92    NVarchar,
93    /// A string value with UTF-16 encoding.
94    NChar,
95    /// A XML value.
96    Xml,
97    /// User-defined type.
98    Udt,
99    /// A text value (deprecated).
100    Text,
101    /// A image value (deprecated).
102    Image,
103    /// A text value with UTF-16 encoding (deprecated).
104    NText,
105    /// An SQL variant type.
106    SSVariant,
107}
108
109impl From<&TypeInfo> for ColumnType {
110    fn from(ti: &TypeInfo) -> Self {
111        match ti {
112            TypeInfo::FixedLen(flt) => match flt {
113                FixedLenType::Int1 => Self::Int1,
114                FixedLenType::Bit => Self::Bit,
115                FixedLenType::Int2 => Self::Int2,
116                FixedLenType::Int4 => Self::Int4,
117                FixedLenType::Datetime4 => Self::Datetime4,
118                FixedLenType::Float4 => Self::Float4,
119                FixedLenType::Money => Self::Money,
120                FixedLenType::Datetime => Self::Datetime,
121                FixedLenType::Float8 => Self::Float8,
122                FixedLenType::Money4 => Self::Money4,
123                FixedLenType::Int8 => Self::Int8,
124                FixedLenType::Null => Self::Null,
125            },
126            TypeInfo::VarLenSized(cx) => match cx.r#type() {
127                VarLenType::Guid => Self::Guid,
128                VarLenType::Intn => match cx.len() {
129                    1 => Self::Int1,
130                    2 => Self::Int2,
131                    4 => Self::Int4,
132                    8 => Self::Int8,
133                    _ => Self::Intn,
134                },
135                VarLenType::Bitn => Self::Bitn,
136                VarLenType::Decimaln => Self::Decimaln,
137                VarLenType::Numericn => Self::Numericn,
138                VarLenType::Floatn => match cx.len() {
139                    4 => Self::Float4,
140                    8 => Self::Float8,
141                    _ => Self::Floatn,
142                },
143                VarLenType::Money => match cx.len() {
144                    4 => Self::Money4,
145                    _ => Self::Money,
146                },
147                // 8 bytes stays `Datetimen`, which callers already match on.
148                VarLenType::Datetimen => match cx.len() {
149                    4 => Self::Datetime4,
150                    _ => Self::Datetimen,
151                },
152                #[cfg(feature = "tds73")]
153                VarLenType::Daten => Self::Daten,
154                #[cfg(feature = "tds73")]
155                VarLenType::Timen => Self::Timen,
156                #[cfg(feature = "tds73")]
157                VarLenType::Datetime2 => Self::Datetime2,
158                #[cfg(feature = "tds73")]
159                VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
160                VarLenType::BigVarBin => Self::BigVarBin,
161                VarLenType::BigVarChar => Self::BigVarChar,
162                VarLenType::BigBinary => Self::BigBinary,
163                VarLenType::BigChar => Self::BigChar,
164                VarLenType::NVarchar => Self::NVarchar,
165                VarLenType::NChar => Self::NChar,
166                VarLenType::Xml => Self::Xml,
167                VarLenType::Udt => Self::Udt,
168                VarLenType::Text => Self::Text,
169                VarLenType::Image => Self::Image,
170                VarLenType::NText => Self::NText,
171                VarLenType::SSVariant => Self::SSVariant,
172            },
173            TypeInfo::VarLenSizedPrecision { ty, .. } => match ty {
174                VarLenType::Guid => Self::Guid,
175                VarLenType::Intn => Self::Intn,
176                VarLenType::Bitn => Self::Bitn,
177                VarLenType::Decimaln => Self::Decimaln,
178                VarLenType::Numericn => Self::Numericn,
179                VarLenType::Floatn => Self::Floatn,
180                VarLenType::Money => Self::Money,
181                VarLenType::Datetimen => Self::Datetimen,
182                #[cfg(feature = "tds73")]
183                VarLenType::Daten => Self::Daten,
184                #[cfg(feature = "tds73")]
185                VarLenType::Timen => Self::Timen,
186                #[cfg(feature = "tds73")]
187                VarLenType::Datetime2 => Self::Datetime2,
188                #[cfg(feature = "tds73")]
189                VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
190                VarLenType::BigVarBin => Self::BigVarBin,
191                VarLenType::BigVarChar => Self::BigVarChar,
192                VarLenType::BigBinary => Self::BigBinary,
193                VarLenType::BigChar => Self::BigChar,
194                VarLenType::NVarchar => Self::NVarchar,
195                VarLenType::NChar => Self::NChar,
196                VarLenType::Xml => Self::Xml,
197                VarLenType::Udt => Self::Udt,
198                VarLenType::Text => Self::Text,
199                VarLenType::Image => Self::Image,
200                VarLenType::NText => Self::NText,
201                VarLenType::SSVariant => Self::SSVariant,
202            },
203            TypeInfo::Xml { .. } => Self::Xml,
204            TypeInfo::Udt(_) => Self::Udt,
205        }
206    }
207}
208
209/// A row of data from a query.
210///
211/// Data can be accessed either by copying through [`get`] or [`try_get`]
212/// methods, or moving by value using the [`IntoIterator`] implementation.
213///
214/// ```
215/// # use tds_driver::{Config, FromSqlOwned};
216/// # use tokio_util::compat::TokioAsyncWriteCompatExt;
217/// # use std::env;
218/// # #[tokio::main]
219/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
220/// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
221/// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
222/// # );
223/// # let config = Config::from_ado_string(&c_str)?;
224/// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
225/// # tcp.set_nodelay(true)?;
226/// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
227/// // by-reference
228/// let row = client
229///     .query("SELECT @P1 AS col1", &[&"test"])
230///     .await?
231///     .into_row()
232///     .await?
233///     .unwrap();
234///
235/// assert_eq!(Some("test"), row.get("col1"));
236///
237/// // ...or by-value
238/// let row = client
239///     .query("SELECT @P1 AS col1", &[&"test"])
240///     .await?
241///     .into_row()
242///     .await?
243///     .unwrap();
244///
245/// for val in row.into_iter() {
246///     assert_eq!(
247///         Some(String::from("test")),
248///         String::from_sql_owned(val)?
249///     )
250/// }
251/// # Ok(())
252/// # }
253/// ```
254///
255/// [`get`]: #method.get
256/// [`try_get`]: #method.try_get
257/// [`IntoIterator`]: #impl-IntoIterator
258#[derive(Debug)]
259#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
260pub struct Row {
261    pub(crate) columns: Arc<Vec<Column>>,
262    pub(crate) data: TokenRow<'static>,
263    pub(crate) result_index: usize,
264}
265
266/// A type that can address a column within a [`Row`], either by its zero-based
267/// position (`usize`) or by name (`&str`).
268///
269/// Implement this for a custom column identifier (for example a generated
270/// column-name enum) to index rows with it via [`Row::get`]/[`Row::try_get`].
271pub trait QueryIdx
272where
273    Self: Display,
274{
275    /// Resolves this index to the column's zero-based position in `row`, or
276    /// `None` if it does not name/point to a column in the row.
277    fn idx(&self, row: &Row) -> Option<usize>;
278}
279
280impl QueryIdx for usize {
281    fn idx(&self, row: &Row) -> Option<usize> {
282        if *self < row.columns.len() {
283            Some(*self)
284        } else {
285            None
286        }
287    }
288}
289
290impl QueryIdx for &str {
291    fn idx(&self, row: &Row) -> Option<usize> {
292        // Allow matching a column selected with a Rust raw identifier (e.g.
293        // `r#type`) against the plain SQL column name (`type`).
294        let name = self.strip_prefix("r#").unwrap_or(self);
295        row.columns.iter().position(|c| c.name() == name)
296    }
297}
298
299impl Row {
300    /// Columns defining the row data. Columns listed here are in the same order
301    /// as the resulting data.
302    ///
303    /// # Example
304    ///
305    /// ```
306    /// # use tds_driver::Config;
307    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
308    /// # use std::env;
309    /// # #[tokio::main]
310    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
311    /// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
312    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
313    /// # );
314    /// # let config = Config::from_ado_string(&c_str)?;
315    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
316    /// # tcp.set_nodelay(true)?;
317    /// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
318    /// let row = client
319    ///     .query("SELECT 1 AS foo, 2 AS bar", &[])
320    ///     .await?
321    ///     .into_row()
322    ///     .await?
323    ///     .unwrap();
324    ///
325    /// assert_eq!("foo", row.columns()[0].name());
326    /// assert_eq!("bar", row.columns()[1].name());
327    /// # Ok(())
328    /// # }
329    /// ```
330    pub fn columns(&self) -> &[Column] {
331        &self.columns
332    }
333
334    /// Return an iterator over row column-value pairs.
335    pub fn cells(&self) -> impl Iterator<Item = (&Column, &ColumnData<'static>)> {
336        self.columns().iter().zip(self.data.iter())
337    }
338
339    /// The result set number, starting from zero and increasing if the stream
340    /// has results from more than one query.
341    pub fn result_index(&self) -> usize {
342        self.result_index
343    }
344
345    /// Returns the number of columns in the row.
346    ///
347    /// # Example
348    ///
349    /// ```
350    /// # use tds_driver::Config;
351    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
352    /// # use std::env;
353    /// # #[tokio::main]
354    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
355    /// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
356    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
357    /// # );
358    /// # let config = Config::from_ado_string(&c_str)?;
359    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
360    /// # tcp.set_nodelay(true)?;
361    /// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
362    /// let row = client
363    ///     .query("SELECT 1, 2", &[])
364    ///     .await?
365    ///     .into_row()
366    ///     .await?
367    ///     .unwrap();
368    ///
369    /// assert_eq!(2, row.len());
370    /// # Ok(())
371    /// # }
372    /// ```
373    #[allow(clippy::len_without_is_empty)]
374    pub fn len(&self) -> usize {
375        self.data.len()
376    }
377
378    /// Retrieve a column value for a given column index, which can either be
379    /// the zero-indexed position or the name of the column.
380    ///
381    /// # Example
382    ///
383    /// ```
384    /// # use tds_driver::Config;
385    /// # use tokio_util::compat::TokioAsyncWriteCompatExt;
386    /// # use std::env;
387    /// # #[tokio::main]
388    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
389    /// # let c_str = env::var("TDS_DRIVER_TEST_CONNECTION_STRING").unwrap_or(
390    /// #     "server=tcp:localhost,1433;integratedSecurity=true;TrustServerCertificate=true".to_owned(),
391    /// # );
392    /// # let config = Config::from_ado_string(&c_str)?;
393    /// # let tcp = tokio::net::TcpStream::connect(config.get_addr()).await?;
394    /// # tcp.set_nodelay(true)?;
395    /// # let mut client = tds_driver::Client::connect(config, tcp.compat_write()).await?;
396    /// let row = client
397    ///     .query("SELECT @P1 AS col1", &[&1i32])
398    ///     .await?
399    ///     .into_row()
400    ///     .await?
401    ///     .unwrap();
402    ///
403    /// assert_eq!(Some(1i32), row.get(0));
404    /// assert_eq!(Some(1i32), row.get("col1"));
405    /// # Ok(())
406    /// # }
407    /// ```
408    ///
409    /// # Panics
410    ///
411    /// - The requested type conversion (SQL->Rust) is not possible.
412    /// - The given index is out of bounds (column does not exist).
413    ///
414    /// Use [`try_get`] for a non-panicking version of the function.
415    ///
416    /// [`try_get`]: #method.try_get
417    #[track_caller]
418    pub fn get<'a, R, I>(&'a self, idx: I) -> Option<R>
419    where
420        R: FromSql<'a>,
421        I: QueryIdx,
422    {
423        self.try_get(idx).unwrap()
424    }
425
426    /// Retrieve a column's value for a given column index.
427    #[track_caller]
428    pub fn try_get<'a, R, I>(&'a self, idx: I) -> crate::Result<Option<R>>
429    where
430        R: FromSql<'a>,
431        I: QueryIdx,
432    {
433        let data = self.get_column_data(idx)?;
434
435        R::from_sql(data)
436    }
437
438    /// Retrieve a column's data for a given column index.
439    #[track_caller]
440    pub fn get_column_data<I>(&self, idx: I) -> crate::Result<&ColumnData<'static>>
441    where
442        I: QueryIdx,
443    {
444        let idx = idx.idx(self).ok_or_else(|| {
445            Error::Conversion(format!("Could not find column with index {}", idx).into())
446        })?;
447
448        // `idx` was validated against the column metadata; the cell should exist,
449        // but a malformed ROW/NBCROW with fewer cells than columns must not
450        // panic here — return an error instead of unwrapping.
451        self.data.get(idx).ok_or_else(|| {
452            Error::Protocol(format!("row has no data for column index {idx}").into())
453        })
454    }
455
456    /// Consumes the row, returning the underlying [`TokenRow`] holding the raw
457    /// column data as received from the server.
458    ///
459    /// This is useful when direct access to the raw [`ColumnData`] values is
460    /// needed instead of converting them through [`get`] or [`try_get`].
461    ///
462    /// [`get`]: #method.get
463    /// [`try_get`]: #method.try_get
464    pub fn into_token_row(self) -> TokenRow<'static> {
465        self.data
466    }
467}
468
469impl IntoIterator for Row {
470    type Item = ColumnData<'static>;
471    type IntoIter = std::vec::IntoIter<Self::Item>;
472
473    fn into_iter(self) -> Self::IntoIter {
474        self.data.into_iter()
475    }
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    fn make_row() -> Row {
483        let columns = Arc::new(vec![
484            Column::new("foo".to_string(), ColumnType::Int4),
485            Column::new("type".to_string(), ColumnType::Int4),
486        ]);
487
488        let mut data = TokenRow::new();
489        data.push(ColumnData::I32(Some(1)));
490        data.push(ColumnData::I32(Some(2)));
491
492        Row {
493            columns,
494            data,
495            result_index: 0,
496        }
497    }
498
499    #[test]
500    fn result_index_reflects_the_field() {
501        // A non-zero index pins `result_index()` (a `-> 0` mutant would pass if
502        // every fixture used index 0).
503        let columns = Arc::new(vec![Column::new("c".to_string(), ColumnType::Int4)]);
504        let mut data = TokenRow::new();
505        data.push(ColumnData::I32(Some(1)));
506        let row = Row {
507            columns,
508            data,
509            result_index: 3,
510        };
511        assert_eq!(row.result_index(), 3);
512    }
513
514    // Regression test for #211: an out-of-range usize index must not panic.
515    #[test]
516    fn try_get_out_of_range_index_returns_none() {
517        let row = make_row();
518
519        assert_eq!(None, 2usize.idx(&row));
520        assert_eq!(Some(0), 0usize.idx(&row));
521
522        let value: crate::Result<Option<i32>> = row.try_get(5usize);
523        assert!(value.is_err());
524    }
525
526    // Regression test for #382: a raw-identifier column name (`r#type`) must
527    // match the plain SQL column name (`type`).
528    #[test]
529    fn raw_identifier_column_name_matches() {
530        let row = make_row();
531
532        assert_eq!(Some(1), "type".idx(&row));
533        assert_eq!(Some(1), "r#type".idx(&row));
534        assert_eq!(Some(0), "r#foo".idx(&row));
535        assert_eq!(None, "r#missing".idx(&row));
536
537        assert_eq!(Some(2i32), row.get::<i32, _>("r#type"));
538    }
539
540    #[test]
541    fn row_accessors() {
542        let row = make_row();
543
544        assert_eq!(2, row.columns().len());
545        assert_eq!(2, row.len());
546        assert_eq!(0, row.result_index());
547
548        let cells: Vec<_> = row.cells().collect();
549        assert_eq!(2, cells.len());
550        assert_eq!("foo", cells[0].0.name());
551
552        let token_row = row.into_token_row();
553        assert_eq!(2, token_row.len());
554    }
555
556    #[test]
557    fn row_into_iterator_yields_column_data() {
558        let row = make_row();
559        let values: Vec<_> = row.into_iter().collect();
560        assert_eq!(
561            vec![ColumnData::I32(Some(1)), ColumnData::I32(Some(2))],
562            values
563        );
564    }
565
566    #[test]
567    fn get_column_data_missing_cell_errors() {
568        let columns = Arc::new(vec![
569            Column::new("a".to_string(), ColumnType::Int4),
570            Column::new("b".to_string(), ColumnType::Int4),
571        ]);
572
573        // Malformed row: metadata says 2 columns, but only 1 cell present.
574        let mut data = TokenRow::new();
575        data.push(ColumnData::I32(Some(1)));
576
577        let row = Row {
578            columns,
579            data,
580            result_index: 0,
581        };
582
583        let err = row.get_column_data(1usize).unwrap_err();
584        assert!(format!("{}", err).contains("row has no data for column index"));
585    }
586
587    #[test]
588    fn column_new_and_accessors() {
589        let column = Column::new("id".to_string(), ColumnType::Int8);
590        assert_eq!("id", column.name());
591        assert_eq!(ColumnType::Int8, column.column_type());
592    }
593
594    #[test]
595    fn column_type_from_fixed_len_type_info() {
596        use crate::tds::codec::FixedLenType;
597
598        let cases = [
599            (FixedLenType::Int1, ColumnType::Int1),
600            (FixedLenType::Bit, ColumnType::Bit),
601            (FixedLenType::Int2, ColumnType::Int2),
602            (FixedLenType::Int4, ColumnType::Int4),
603            (FixedLenType::Datetime4, ColumnType::Datetime4),
604            (FixedLenType::Float4, ColumnType::Float4),
605            (FixedLenType::Money, ColumnType::Money),
606            (FixedLenType::Datetime, ColumnType::Datetime),
607            (FixedLenType::Float8, ColumnType::Float8),
608            (FixedLenType::Money4, ColumnType::Money4),
609            (FixedLenType::Int8, ColumnType::Int8),
610            (FixedLenType::Null, ColumnType::Null),
611        ];
612
613        for (flt, expected) in cases {
614            let ti = TypeInfo::FixedLen(flt);
615            assert_eq!(ColumnType::from(&ti), expected);
616        }
617    }
618
619    #[test]
620    fn column_type_from_var_len_sized_type_info() {
621        use crate::tds::codec::VarLenType;
622        use crate::VarLenContext;
623
624        let cases = [
625            (VarLenType::Guid, 16, ColumnType::Guid),
626            (VarLenType::Intn, 1, ColumnType::Int1),
627            (VarLenType::Intn, 2, ColumnType::Int2),
628            (VarLenType::Intn, 4, ColumnType::Int4),
629            (VarLenType::Intn, 8, ColumnType::Int8),
630            (VarLenType::Intn, 3, ColumnType::Intn),
631            (VarLenType::Bitn, 1, ColumnType::Bitn),
632            (VarLenType::Decimaln, 17, ColumnType::Decimaln),
633            (VarLenType::Numericn, 17, ColumnType::Numericn),
634            (VarLenType::Floatn, 4, ColumnType::Float4),
635            (VarLenType::Floatn, 8, ColumnType::Float8),
636            (VarLenType::Floatn, 2, ColumnType::Floatn),
637            (VarLenType::Money, 4, ColumnType::Money4),
638            (VarLenType::Money, 8, ColumnType::Money),
639            (VarLenType::Datetimen, 4, ColumnType::Datetime4),
640            (VarLenType::Datetimen, 8, ColumnType::Datetimen),
641            (VarLenType::BigVarBin, 8000, ColumnType::BigVarBin),
642            (VarLenType::BigVarChar, 8000, ColumnType::BigVarChar),
643            (VarLenType::BigBinary, 8000, ColumnType::BigBinary),
644            (VarLenType::BigChar, 8000, ColumnType::BigChar),
645            (VarLenType::NVarchar, 4000, ColumnType::NVarchar),
646            (VarLenType::NChar, 4000, ColumnType::NChar),
647            (VarLenType::Xml, 0, ColumnType::Xml),
648            (VarLenType::Udt, 0, ColumnType::Udt),
649            (VarLenType::Text, 0, ColumnType::Text),
650            (VarLenType::Image, 0, ColumnType::Image),
651            (VarLenType::NText, 0, ColumnType::NText),
652            (VarLenType::SSVariant, 0, ColumnType::SSVariant),
653        ];
654
655        for (ty, len, expected) in cases {
656            let ti = TypeInfo::VarLenSized(VarLenContext::new(ty, len, None));
657            assert_eq!(ColumnType::from(&ti), expected, "{:?} len {}", ty, len);
658        }
659    }
660
661    #[test]
662    fn column_type_from_var_len_sized_precision_type_info() {
663        use crate::tds::codec::VarLenType;
664
665        let cases = [
666            (VarLenType::Guid, ColumnType::Guid),
667            (VarLenType::Intn, ColumnType::Intn),
668            (VarLenType::Bitn, ColumnType::Bitn),
669            (VarLenType::Decimaln, ColumnType::Decimaln),
670            (VarLenType::Numericn, ColumnType::Numericn),
671            (VarLenType::Floatn, ColumnType::Floatn),
672            (VarLenType::Money, ColumnType::Money),
673            (VarLenType::Datetimen, ColumnType::Datetimen),
674            (VarLenType::BigVarBin, ColumnType::BigVarBin),
675            (VarLenType::BigVarChar, ColumnType::BigVarChar),
676            (VarLenType::BigBinary, ColumnType::BigBinary),
677            (VarLenType::BigChar, ColumnType::BigChar),
678            (VarLenType::NVarchar, ColumnType::NVarchar),
679            (VarLenType::NChar, ColumnType::NChar),
680            (VarLenType::Xml, ColumnType::Xml),
681            (VarLenType::Udt, ColumnType::Udt),
682            (VarLenType::Text, ColumnType::Text),
683            (VarLenType::Image, ColumnType::Image),
684            (VarLenType::NText, ColumnType::NText),
685            (VarLenType::SSVariant, ColumnType::SSVariant),
686        ];
687
688        for (ty, expected) in cases {
689            let ti = TypeInfo::VarLenSizedPrecision {
690                ty,
691                size: 38,
692                precision: 38,
693                scale: 2,
694            };
695            assert_eq!(ColumnType::from(&ti), expected, "{:?}", ty);
696        }
697    }
698
699    #[test]
700    fn column_type_from_xml_and_udt_type_info() {
701        use crate::tds::codec::UdtInfo;
702        use crate::tds::xml::XmlSchema;
703        use std::sync::Arc as StdArc;
704
705        let ti = TypeInfo::Xml {
706            schema: None::<StdArc<XmlSchema>>,
707            size: 0,
708        };
709        assert_eq!(ColumnType::from(&ti), ColumnType::Xml);
710
711        let ti = TypeInfo::Udt(UdtInfo {
712            max_byte_size: 0xffff,
713            db_name: "db".to_string(),
714            schema_name: "dbo".to_string(),
715            type_name: "geometry".to_string(),
716            assembly_qualified_name: "asm".to_string(),
717        });
718        assert_eq!(ColumnType::from(&ti), ColumnType::Udt);
719    }
720
721    #[cfg(feature = "tds73")]
722    #[test]
723    fn column_type_from_var_len_sized_tds73_type_info() {
724        use crate::tds::codec::VarLenType;
725        use crate::VarLenContext;
726
727        let cases = [
728            (VarLenType::Daten, ColumnType::Daten),
729            (VarLenType::Timen, ColumnType::Timen),
730            (VarLenType::Datetime2, ColumnType::Datetime2),
731            (VarLenType::DatetimeOffsetn, ColumnType::DatetimeOffsetn),
732        ];
733
734        for (ty, expected) in cases {
735            let ti = TypeInfo::VarLenSized(VarLenContext::new(ty, 8, None));
736            assert_eq!(ColumnType::from(&ti), expected, "{:?}", ty);
737        }
738    }
739
740    #[cfg(feature = "tds73")]
741    #[test]
742    fn column_type_from_var_len_sized_precision_tds73_type_info() {
743        use crate::tds::codec::VarLenType;
744
745        let cases = [
746            (VarLenType::Daten, ColumnType::Daten),
747            (VarLenType::Timen, ColumnType::Timen),
748            (VarLenType::Datetime2, ColumnType::Datetime2),
749            (VarLenType::DatetimeOffsetn, ColumnType::DatetimeOffsetn),
750        ];
751
752        for (ty, expected) in cases {
753            let ti = TypeInfo::VarLenSizedPrecision {
754                ty,
755                size: 8,
756                precision: 0,
757                scale: 7,
758            };
759            assert_eq!(ColumnType::from(&ti), expected, "{:?}", ty);
760        }
761    }
762}