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