use crate::SqlReadBytes;
const STATUS_EXPRESSION: u8 = 0x04;
const STATUS_KEY: u8 = 0x08;
const STATUS_HIDDEN: u8 = 0x10;
const STATUS_DIFFERENT_NAME: u8 = 0x20;
#[allow(dead_code)] #[derive(Debug, Clone)]
pub struct ColInfo {
pub(crate) col_num: u8,
pub(crate) table_num: u8,
pub(crate) status: u8,
pub(crate) col_name: Option<String>,
}
#[allow(dead_code)] impl ColInfo {
pub(crate) fn is_expression(&self) -> bool {
self.status & STATUS_EXPRESSION != 0
}
pub(crate) fn is_key(&self) -> bool {
self.status & STATUS_KEY != 0
}
pub(crate) fn is_hidden(&self) -> bool {
self.status & STATUS_HIDDEN != 0
}
pub(crate) fn has_different_name(&self) -> bool {
self.status & STATUS_DIFFERENT_NAME != 0
}
}
#[allow(dead_code)] #[derive(Debug, Clone)]
pub struct TokenColInfo {
pub(crate) columns: Vec<ColInfo>,
}
impl TokenColInfo {
pub(crate) async fn decode<R>(src: &mut R) -> crate::Result<Self>
where
R: SqlReadBytes + Unpin,
{
let length = src.read_u16_le().await? as usize;
let mut consumed = 0usize;
let mut columns = Vec::new();
while consumed < length {
let col_num = src.read_u8().await?;
let table_num = src.read_u8().await?;
let status = src.read_u8().await?;
consumed += 3;
let col_name = if status & STATUS_DIFFERENT_NAME != 0 {
let char_len = src.read_u8().await? as usize;
consumed += 1;
let mut units = Vec::with_capacity(char_len);
for _ in 0..char_len {
units.push(src.read_u16_le().await?);
}
consumed += char_len * 2;
Some(String::from_utf16_lossy(&units))
} else {
None
};
columns.push(ColInfo {
col_num,
table_num,
status,
col_name,
});
}
Ok(TokenColInfo { columns })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
#[tokio::test]
async fn decode_col_info_with_and_without_names() {
let mut buf = BytesMut::new();
let mut body = BytesMut::new();
body.put_u8(1); body.put_u8(1); body.put_u8(STATUS_KEY);
body.put_u8(2); body.put_u8(0); body.put_u8(STATUS_EXPRESSION | STATUS_DIFFERENT_NAME); body.put_u8(2); body.put_u16_le(u16::from(b'I'));
body.put_u16_le(u16::from(b'd'));
buf.put_u16_le(body.len() as u16);
buf.extend_from_slice(&body);
let mut reader = buf.into_sql_read_bytes();
let token = TokenColInfo::decode(&mut reader).await.unwrap();
assert_eq!(token.columns.len(), 2);
let first = &token.columns[0];
assert_eq!(first.col_num, 1);
assert_eq!(first.table_num, 1);
assert!(first.is_key());
assert!(!first.has_different_name());
assert_eq!(first.col_name, None);
let second = &token.columns[1];
assert_eq!(second.col_num, 2);
assert_eq!(second.table_num, 0);
assert!(second.is_expression());
assert!(second.has_different_name());
assert!(!second.is_hidden());
assert_eq!(second.col_name.as_deref(), Some("Id"));
}
#[test]
fn colinfo_status_predicates() {
let expr = ColInfo {
col_num: 1,
table_num: 0,
status: STATUS_EXPRESSION,
col_name: None,
};
assert!(expr.is_expression());
assert!(!expr.is_key());
assert!(!expr.is_hidden());
let key = ColInfo {
col_num: 1,
table_num: 0,
status: STATUS_KEY,
col_name: None,
};
assert!(!key.is_expression());
assert!(key.is_key());
assert!(!key.is_hidden());
let hidden = ColInfo {
col_num: 1,
table_num: 0,
status: STATUS_HIDDEN,
col_name: None,
};
assert!(!hidden.is_expression());
assert!(!hidden.is_key());
assert!(hidden.is_hidden());
}
#[tokio::test]
async fn decode_col_info_advances_consumed_by_name_bytes() {
let mut body = BytesMut::new();
body.put_u8(1); body.put_u8(0); body.put_u8(STATUS_EXPRESSION | STATUS_DIFFERENT_NAME); body.put_u8(3); body.put_u16_le(u16::from(b'a'));
body.put_u16_le(u16::from(b'b'));
body.put_u16_le(u16::from(b'c'));
body.put_u8(2); body.put_u8(1); body.put_u8(STATUS_KEY);
let mut buf = BytesMut::new();
buf.put_u16_le(body.len() as u16);
buf.extend_from_slice(&body);
let mut reader = buf.into_sql_read_bytes();
let token = TokenColInfo::decode(&mut reader).await.unwrap();
assert_eq!(token.columns.len(), 2);
assert_eq!(token.columns[0].col_num, 1);
assert_eq!(token.columns[0].col_name.as_deref(), Some("abc"));
assert_eq!(token.columns[1].col_num, 2);
assert!(token.columns[1].is_key());
}
}