Skip to main content

tiberius/tds/codec/token/
token_alt_row.rs

1use crate::{
2    tds::codec::{ColumnData, TokenAltMetaData},
3    SqlReadBytes,
4};
5
6/// A row of computed data produced by a COMPUTE (BY) clause (`ALTROW`, token
7/// `0xD3`).
8///
9/// The row refers back, through [`id`](Self::id), to the
10/// [`TokenAltMetaData`](crate::tds::codec::TokenAltMetaData) that describes the
11/// type of each value. See [MS-TDS] section 2.2.7.2.
12///
13/// [MS-TDS]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/
14#[derive(Debug, Clone)]
15pub struct TokenAltRow<'a> {
16    /// Identifies the COMPUTE clause (and thus the `ALTMETADATA`) this row
17    /// belongs to.
18    pub id: u16,
19    data: Vec<ColumnData<'a>>,
20}
21
22impl<'a> TokenAltRow<'a> {
23    /// The id of the COMPUTE clause this row belongs to.
24    pub fn id(&self) -> u16 {
25        self.id
26    }
27
28    /// The number of computed columns in the row.
29    pub fn len(&self) -> usize {
30        self.data.len()
31    }
32
33    /// True if the row has no columns.
34    pub fn is_empty(&self) -> bool {
35        self.data.is_empty()
36    }
37
38    /// Returns an iterator over the computed column values.
39    pub fn iter(&self) -> std::slice::Iter<'_, ColumnData<'a>> {
40        self.data.iter()
41    }
42
43    /// Gets the computed value at the given index, `None` if out of bounds.
44    pub fn get(&self, index: usize) -> Option<&ColumnData<'a>> {
45        self.data.get(index)
46    }
47}
48
49impl TokenAltRow<'static> {
50    /// Decodes the column values of an `ALTROW` for the COMPUTE clause `id`,
51    /// using the previously received [`TokenAltMetaData`] that describes it.
52    ///
53    /// The `id` is read from the wire separately (by the token stream) so that
54    /// the correct metadata can be looked up before the values are parsed.
55    pub(crate) async fn decode<R>(
56        src: &mut R,
57        id: u16,
58        meta: &TokenAltMetaData<'static>,
59    ) -> crate::Result<Self>
60    where
61        R: SqlReadBytes + Unpin,
62    {
63        let mut data = Vec::with_capacity(meta.columns.len());
64
65        for column in meta.columns.iter() {
66            data.push(ColumnData::decode(src, &column.base.ty).await?);
67        }
68
69        Ok(TokenAltRow { id, data })
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use std::borrow::Cow;
76
77    use super::*;
78    use crate::{
79        sql_read_bytes::test_utils::IntoSqlReadBytes, AltMetaDataColumn, BaseMetaDataColumn,
80        ColumnFlag, FixedLenType, TypeInfo,
81    };
82    use bytes::{BufMut, BytesMut};
83
84    fn int_alt_meta() -> TokenAltMetaData<'static> {
85        TokenAltMetaData {
86            id: 1,
87            by_columns: vec![],
88            columns: vec![AltMetaDataColumn {
89                op: 0x4f, // SUM
90                operand: 1,
91                base: BaseMetaDataColumn {
92                    flags: ColumnFlag::Nullable.into(),
93                    ty: TypeInfo::FixedLen(FixedLenType::Int4),
94                },
95                col_name: Cow::from("sum"),
96            }],
97        }
98    }
99
100    #[tokio::test]
101    async fn decode_alt_row_reads_values() {
102        let meta = int_alt_meta();
103
104        let mut buf = BytesMut::new();
105        buf.put_i32_le(42); // the single Int4 computed value
106
107        let mut reader = buf.into_sql_read_bytes();
108        let row = TokenAltRow::decode(&mut reader, meta.id, &meta)
109            .await
110            .unwrap();
111
112        assert_eq!(1, row.id());
113        assert_eq!(1, row.len());
114        assert!(matches!(row.get(0), Some(ColumnData::I32(Some(42)))));
115    }
116
117    #[test]
118    fn accessors_reflect_id_and_columns() {
119        // Empty row: id must be the stored value (not a hardcoded 1), len 0,
120        // is_empty true.
121        let empty = TokenAltRow {
122            id: 5,
123            data: vec![],
124        };
125        assert_eq!(empty.id(), 5);
126        assert_eq!(empty.len(), 0);
127        assert!(empty.is_empty());
128
129        // Non-empty row with a distinct id and two columns: len 2, is_empty
130        // false.
131        let filled = TokenAltRow {
132            id: 9,
133            data: vec![ColumnData::I32(Some(10)), ColumnData::I32(Some(20))],
134        };
135        assert_eq!(filled.id(), 9);
136        assert_eq!(filled.len(), 2);
137        assert!(!filled.is_empty());
138    }
139}