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