Skip to main content

tiberius/tds/codec/
type_info.rs

1use asynchronous_codec::BytesMut;
2use bytes::BufMut;
3
4use crate::{tds::Collation, xml::XmlSchema, Error, SqlReadBytes};
5use std::{convert::TryFrom, sync::Arc, usize};
6
7use super::Encode;
8
9/// A length of a column in bytes or characters.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TypeLength {
12    /// The number of bytes (or characters) reserved in the column.
13    Limited(u16),
14    /// Unlimited, stored in the heap outside of the row.
15    Max,
16}
17
18/// Describes a type of a column.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum TypeInfo {
21    /// A fixed-length TDS type.
22    FixedLen(FixedLenType),
23    /// A variable-length TDS type with optional collation metadata.
24    VarLenSized(VarLenContext),
25    /// A variable-length TDS type that also carries precision and scale.
26    VarLenSizedPrecision {
27        /// The variable-length TDS type tag.
28        ty: VarLenType,
29        /// The storage size in bytes.
30        size: usize,
31        /// Numeric precision.
32        precision: u8,
33        /// Numeric scale.
34        scale: u8,
35    },
36    /// XML metadata.
37    Xml {
38        /// Optional XML schema metadata.
39        schema: Option<Arc<XmlSchema>>,
40        /// The XML type size marker.
41        size: usize,
42    },
43}
44
45/// Detailed metadata for a variable-length TDS type.
46#[derive(Clone, Debug, Copy, PartialEq, Eq)]
47pub struct VarLenContext {
48    r#type: VarLenType,
49    len: usize,
50    collation: Option<Collation>,
51}
52
53impl VarLenContext {
54    /// Creates a variable-length type context.
55    pub fn new(r#type: VarLenType, len: usize, collation: Option<Collation>) -> Self {
56        Self {
57            r#type,
58            len,
59            collation,
60        }
61    }
62
63    /// Get the var len context's r#type.
64    pub fn r#type(&self) -> VarLenType {
65        self.r#type
66    }
67
68    /// Get the var len context's len.
69    pub fn len(&self) -> usize {
70        self.len
71    }
72
73    /// Get the var len context's collation.
74    pub fn collation(&self) -> Option<Collation> {
75        self.collation
76    }
77}
78
79impl Encode<BytesMut> for VarLenContext {
80    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
81        dst.put_u8(self.r#type() as u8);
82
83        // length
84        match self.r#type {
85            #[cfg(feature = "tds73")]
86            VarLenType::Daten => {}
87            #[cfg(feature = "tds73")]
88            VarLenType::Timen | VarLenType::DatetimeOffsetn | VarLenType::Datetime2 => {
89                dst.put_u8(self.len() as u8);
90            }
91            VarLenType::Bitn
92            | VarLenType::Intn
93            | VarLenType::Floatn
94            | VarLenType::Decimaln
95            | VarLenType::Numericn
96            | VarLenType::Guid
97            | VarLenType::Money
98            | VarLenType::Datetimen => {
99                dst.put_u8(self.len() as u8);
100            }
101            VarLenType::NChar
102            | VarLenType::BigChar
103            | VarLenType::NVarchar
104            | VarLenType::BigVarChar
105            | VarLenType::BigBinary
106            | VarLenType::BigVarBin => {
107                dst.put_u16_le(self.len() as u16);
108            }
109            VarLenType::Image | VarLenType::Text | VarLenType::NText => {
110                dst.put_u32_le(self.len() as u32);
111            }
112            VarLenType::Xml => (),
113            typ => todo!("encoding {:?} is not supported yet", typ),
114        }
115
116        if let Some(collation) = self.collation() {
117            dst.put_u32_le(collation.info());
118            dst.put_u8(collation.sort_id());
119        }
120
121        Ok(())
122    }
123}
124
125uint_enum! {
126    #[repr(u8)]
127    pub enum FixedLenType {
128        Null = 0x1F,
129        Int1 = 0x30,
130        Bit = 0x32,
131        Int2 = 0x34,
132        Int4 = 0x38,
133        Datetime4 = 0x3A,
134        Float4 = 0x3B,
135        Money = 0x3C,
136        Datetime = 0x3D,
137        Float8 = 0x3E,
138        Money4 = 0x7A,
139        Int8 = 0x7F,
140    }
141}
142
143#[cfg(not(feature = "tds73"))]
144uint_enum! {
145    /// 2.2.5.4.2
146    #[repr(u8)]
147    pub enum VarLenType {
148        Guid = 0x24,
149        Intn = 0x26,
150        Bitn = 0x68,
151        Decimaln = 0x6A,
152        Numericn = 0x6C,
153        Floatn = 0x6D,
154        Money = 0x6E,
155        Datetimen = 0x6F,
156        BigVarBin = 0xA5,
157        BigVarChar = 0xA7,
158        BigBinary = 0xAD,
159        BigChar = 0xAF,
160        NVarchar = 0xE7,
161        NChar = 0xEF,
162        Xml = 0xF1,
163        // not supported yet
164        Udt = 0xF0,
165        Text = 0x23,
166        Image = 0x22,
167        NText = 0x63,
168        // not supported yet
169        SSVariant = 0x62, // legacy types (not supported since post-7.2):
170                          // Char = 0x2F,
171                          // Binary = 0x2D,
172                          // VarBinary = 0x25,
173                          // VarChar = 0x27,
174                          // Numeric = 0x3F,
175                          // Decimal = 0x37
176    }
177}
178
179#[cfg(feature = "tds73")]
180uint_enum! {
181    /// 2.2.5.4.2
182    #[repr(u8)]
183    pub enum VarLenType {
184        Guid = 0x24,
185        Intn = 0x26,
186        Bitn = 0x68,
187        Decimaln = 0x6A,
188        Numericn = 0x6C,
189        Floatn = 0x6D,
190        Money = 0x6E,
191        Datetimen = 0x6F,
192        Daten = 0x28,
193        Timen = 0x29,
194        Datetime2 = 0x2A,
195        DatetimeOffsetn = 0x2B,
196        BigVarBin = 0xA5,
197        BigVarChar = 0xA7,
198        BigBinary = 0xAD,
199        BigChar = 0xAF,
200        NVarchar = 0xE7,
201        NChar = 0xEF,
202        Xml = 0xF1,
203        // not supported yet
204        Udt = 0xF0,
205        Text = 0x23,
206        Image = 0x22,
207        NText = 0x63,
208        // not supported yet
209        SSVariant = 0x62, // legacy types (not supported since post-7.2):
210                          // Char = 0x2F,
211                          // Binary = 0x2D,
212                          // VarBinary = 0x25,
213                          // VarChar = 0x27,
214                          // Numeric = 0x3F,
215                          // Decimal = 0x37
216    }
217}
218
219impl Encode<BytesMut> for TypeInfo {
220    fn encode(self, dst: &mut BytesMut) -> crate::Result<()> {
221        match self {
222            TypeInfo::FixedLen(ty) => {
223                dst.put_u8(ty as u8);
224            }
225            TypeInfo::VarLenSized(ctx) => ctx.encode(dst)?,
226            TypeInfo::VarLenSizedPrecision {
227                ty,
228                size,
229                precision,
230                scale,
231            } => {
232                dst.put_u8(ty as u8);
233                dst.put_u8(size as u8);
234                dst.put_u8(precision);
235                dst.put_u8(scale);
236            }
237            TypeInfo::Xml { schema, .. } => {
238                dst.put_u8(VarLenType::Xml as u8);
239
240                if let Some(xs) = schema {
241                    dst.put_u8(1);
242
243                    let db_name_encoded: Vec<u16> = xs.db_name().encode_utf16().collect();
244                    dst.put_u8(db_name_encoded.len() as u8);
245                    for chr in db_name_encoded {
246                        dst.put_u16_le(chr);
247                    }
248
249                    let owner_encoded: Vec<u16> = xs.owner().encode_utf16().collect();
250                    dst.put_u8(owner_encoded.len() as u8);
251                    for chr in owner_encoded {
252                        dst.put_u16_le(chr);
253                    }
254
255                    let collection_encoded: Vec<u16> = xs.collection().encode_utf16().collect();
256                    dst.put_u16_le(collection_encoded.len() as u16);
257                    for chr in collection_encoded {
258                        dst.put_u16_le(chr);
259                    }
260                } else {
261                    dst.put_u8(0);
262                }
263            }
264        }
265
266        Ok(())
267    }
268}
269
270impl TypeInfo {
271    pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
272    where
273        R: SqlReadBytes + Unpin,
274    {
275        let ty = src.read_u8().await?;
276
277        if let Ok(ty) = FixedLenType::try_from(ty) {
278            return Ok(TypeInfo::FixedLen(ty));
279        }
280
281        match VarLenType::try_from(ty) {
282            Err(()) => Err(Error::Protocol(
283                format!("invalid or unsupported column type: {:?}", ty).into(),
284            )),
285            Ok(VarLenType::Xml) => {
286                let has_schema = src.read_u8().await?;
287
288                let schema = if has_schema == 1 {
289                    let db_name = src.read_b_varchar().await?;
290                    let owner = src.read_b_varchar().await?;
291                    let collection = src.read_us_varchar().await?;
292
293                    Some(Arc::new(XmlSchema::new(db_name, owner, collection)))
294                } else {
295                    None
296                };
297
298                Ok(TypeInfo::Xml {
299                    schema,
300                    size: 0xfffffffffffffffe_usize,
301                })
302            }
303            Ok(ty) => {
304                let len = match ty {
305                    #[cfg(feature = "tds73")]
306                    VarLenType::Timen | VarLenType::DatetimeOffsetn | VarLenType::Datetime2 => {
307                        src.read_u8().await? as usize
308                    }
309                    #[cfg(feature = "tds73")]
310                    VarLenType::Daten => 3,
311                    VarLenType::Bitn
312                    | VarLenType::Intn
313                    | VarLenType::Floatn
314                    | VarLenType::Decimaln
315                    | VarLenType::Numericn
316                    | VarLenType::Guid
317                    | VarLenType::Money
318                    | VarLenType::Datetimen => src.read_u8().await? as usize,
319                    VarLenType::NChar
320                    | VarLenType::BigChar
321                    | VarLenType::NVarchar
322                    | VarLenType::BigVarChar
323                    | VarLenType::BigBinary
324                    | VarLenType::BigVarBin => src.read_u16_le().await? as usize,
325                    VarLenType::Image | VarLenType::Text | VarLenType::NText => {
326                        src.read_u32_le().await? as usize
327                    }
328                    _ => todo!("not yet implemented for {:?}", ty),
329                };
330
331                let collation = match ty {
332                    VarLenType::NText
333                    | VarLenType::Text
334                    | VarLenType::BigChar
335                    | VarLenType::NChar
336                    | VarLenType::NVarchar
337                    | VarLenType::BigVarChar => {
338                        let info = src.read_u32_le().await?;
339                        let sort_id = src.read_u8().await?;
340
341                        Some(Collation::new(info, sort_id))
342                    }
343                    _ => None,
344                };
345
346                let vty = match ty {
347                    VarLenType::Decimaln | VarLenType::Numericn => {
348                        let precision = src.read_u8().await?;
349                        let scale = src.read_u8().await?;
350
351                        TypeInfo::VarLenSizedPrecision {
352                            size: len,
353                            ty,
354                            precision,
355                            scale,
356                        }
357                    }
358                    _ => {
359                        let cx = VarLenContext::new(ty, len, collation);
360                        TypeInfo::VarLenSized(cx)
361                    }
362                };
363
364                Ok(vty)
365            }
366        }
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
374
375    #[tokio::test]
376    async fn round_trip() {
377        let types = vec![
378            TypeInfo::Xml {
379                schema: Some(
380                    XmlSchema::new("fake-db-name", "fake-owner", "fake-collection").into(),
381                ),
382                size: 0xfffffffffffffffe_usize,
383            },
384            TypeInfo::Xml {
385                schema: None,
386                size: 0xfffffffffffffffe_usize,
387            },
388            TypeInfo::FixedLen(FixedLenType::Int4),
389            TypeInfo::VarLenSized(VarLenContext::new(
390                VarLenType::NChar,
391                40,
392                Some(Collation::new(13632521, 52)),
393            )),
394        ];
395
396        for ti in types {
397            let mut buf = BytesMut::new();
398
399            ti.clone()
400                .encode(&mut buf)
401                .expect("encode should be successful");
402
403            let nti = TypeInfo::decode(&mut buf.into_sql_read_bytes())
404                .await
405                .expect("decode must succeed");
406
407            assert_eq!(nti, ti)
408        }
409    }
410}