use crate::{
error::Error,
tds::codec::{ColumnData, FixedLenType, TokenRow, TypeInfo, VarLenType},
FromSql,
};
use std::{fmt::Display, sync::Arc};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Column {
pub(crate) name: String,
pub(crate) column_type: ColumnType,
}
impl Column {
pub fn new(name: String, column_type: ColumnType) -> Self {
Self { name, column_type }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn column_type(&self) -> ColumnType {
self.column_type
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ColumnType {
Null,
Bit,
Int1,
Int2,
Int4,
Int8,
Datetime4,
Float4,
Float8,
Money,
Datetime,
Money4,
Guid,
Intn,
Bitn,
Decimaln,
Numericn,
Floatn,
Datetimen,
Daten,
Timen,
Datetime2,
DatetimeOffsetn,
BigVarBin,
BigVarChar,
BigBinary,
BigChar,
NVarchar,
NChar,
Xml,
Udt,
Text,
Image,
NText,
SSVariant,
}
impl From<&TypeInfo> for ColumnType {
fn from(ti: &TypeInfo) -> Self {
match ti {
TypeInfo::FixedLen(flt) => match flt {
FixedLenType::Int1 => Self::Int1,
FixedLenType::Bit => Self::Bit,
FixedLenType::Int2 => Self::Int2,
FixedLenType::Int4 => Self::Int4,
FixedLenType::Datetime4 => Self::Datetime4,
FixedLenType::Float4 => Self::Float4,
FixedLenType::Money => Self::Money,
FixedLenType::Datetime => Self::Datetime,
FixedLenType::Float8 => Self::Float8,
FixedLenType::Money4 => Self::Money4,
FixedLenType::Int8 => Self::Int8,
FixedLenType::Null => Self::Null,
},
TypeInfo::VarLenSized(cx) => match cx.r#type() {
VarLenType::Guid => Self::Guid,
VarLenType::Intn => match cx.len() {
1 => Self::Int1,
2 => Self::Int2,
4 => Self::Int4,
8 => Self::Int8,
_ => Self::Intn,
},
VarLenType::Bitn => Self::Bitn,
VarLenType::Decimaln => Self::Decimaln,
VarLenType::Numericn => Self::Numericn,
VarLenType::Floatn => match cx.len() {
4 => Self::Float4,
8 => Self::Float8,
_ => Self::Floatn,
},
VarLenType::Money => Self::Money,
VarLenType::Datetimen => Self::Datetimen,
#[cfg(feature = "tds73")]
VarLenType::Daten => Self::Daten,
#[cfg(feature = "tds73")]
VarLenType::Timen => Self::Timen,
#[cfg(feature = "tds73")]
VarLenType::Datetime2 => Self::Datetime2,
#[cfg(feature = "tds73")]
VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
VarLenType::BigVarBin => Self::BigVarBin,
VarLenType::BigVarChar => Self::BigVarChar,
VarLenType::BigBinary => Self::BigBinary,
VarLenType::BigChar => Self::BigChar,
VarLenType::NVarchar => Self::NVarchar,
VarLenType::NChar => Self::NChar,
VarLenType::Xml => Self::Xml,
VarLenType::Udt => Self::Udt,
VarLenType::Text => Self::Text,
VarLenType::Image => Self::Image,
VarLenType::NText => Self::NText,
VarLenType::SSVariant => Self::SSVariant,
},
TypeInfo::VarLenSizedPrecision { ty, .. } => match ty {
VarLenType::Guid => Self::Guid,
VarLenType::Intn => Self::Intn,
VarLenType::Bitn => Self::Bitn,
VarLenType::Decimaln => Self::Decimaln,
VarLenType::Numericn => Self::Numericn,
VarLenType::Floatn => Self::Floatn,
VarLenType::Money => Self::Money,
VarLenType::Datetimen => Self::Datetimen,
#[cfg(feature = "tds73")]
VarLenType::Daten => Self::Daten,
#[cfg(feature = "tds73")]
VarLenType::Timen => Self::Timen,
#[cfg(feature = "tds73")]
VarLenType::Datetime2 => Self::Datetime2,
#[cfg(feature = "tds73")]
VarLenType::DatetimeOffsetn => Self::DatetimeOffsetn,
VarLenType::BigVarBin => Self::BigVarBin,
VarLenType::BigVarChar => Self::BigVarChar,
VarLenType::BigBinary => Self::BigBinary,
VarLenType::BigChar => Self::BigChar,
VarLenType::NVarchar => Self::NVarchar,
VarLenType::NChar => Self::NChar,
VarLenType::Xml => Self::Xml,
VarLenType::Udt => Self::Udt,
VarLenType::Text => Self::Text,
VarLenType::Image => Self::Image,
VarLenType::NText => Self::NText,
VarLenType::SSVariant => Self::SSVariant,
},
TypeInfo::Xml { .. } => Self::Xml,
TypeInfo::Udt(_) => Self::Udt,
}
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Row {
pub(crate) columns: Arc<Vec<Column>>,
pub(crate) data: TokenRow<'static>,
pub(crate) result_index: usize,
}
pub trait QueryIdx
where
Self: Display,
{
fn idx(&self, row: &Row) -> Option<usize>;
}
impl QueryIdx for usize {
fn idx(&self, row: &Row) -> Option<usize> {
if *self < row.columns.len() {
Some(*self)
} else {
None
}
}
}
impl QueryIdx for &str {
fn idx(&self, row: &Row) -> Option<usize> {
let name = self.strip_prefix("r#").unwrap_or(self);
row.columns.iter().position(|c| c.name() == name)
}
}
impl Row {
pub fn columns(&self) -> &[Column] {
&self.columns
}
pub fn cells(&self) -> impl Iterator<Item = (&Column, &ColumnData<'static>)> {
self.columns().iter().zip(self.data.iter())
}
pub fn result_index(&self) -> usize {
self.result_index
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.data.len()
}
#[track_caller]
pub fn get<'a, R, I>(&'a self, idx: I) -> Option<R>
where
R: FromSql<'a>,
I: QueryIdx,
{
self.try_get(idx).unwrap()
}
#[track_caller]
pub fn try_get<'a, R, I>(&'a self, idx: I) -> crate::Result<Option<R>>
where
R: FromSql<'a>,
I: QueryIdx,
{
let data = self.get_column_data(idx)?;
R::from_sql(data)
}
#[track_caller]
pub fn get_column_data<I>(&self, idx: I) -> crate::Result<&ColumnData<'static>>
where
I: QueryIdx,
{
let idx = idx.idx(self).ok_or_else(|| {
Error::Conversion(format!("Could not find column with index {}", idx).into())
})?;
self.data.get(idx).ok_or_else(|| {
Error::Protocol(format!("row has no data for column index {idx}").into())
})
}
pub fn into_token_row(self) -> TokenRow<'static> {
self.data
}
}
impl IntoIterator for Row {
type Item = ColumnData<'static>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_row() -> Row {
let columns = Arc::new(vec![
Column::new("foo".to_string(), ColumnType::Int4),
Column::new("type".to_string(), ColumnType::Int4),
]);
let mut data = TokenRow::new();
data.push(ColumnData::I32(Some(1)));
data.push(ColumnData::I32(Some(2)));
Row {
columns,
data,
result_index: 0,
}
}
#[test]
fn try_get_out_of_range_index_returns_none() {
let row = make_row();
assert_eq!(None, 2usize.idx(&row));
assert_eq!(Some(0), 0usize.idx(&row));
let value: crate::Result<Option<i32>> = row.try_get(5usize);
assert!(value.is_err());
}
#[test]
fn raw_identifier_column_name_matches() {
let row = make_row();
assert_eq!(Some(1), "type".idx(&row));
assert_eq!(Some(1), "r#type".idx(&row));
assert_eq!(Some(0), "r#foo".idx(&row));
assert_eq!(None, "r#missing".idx(&row));
assert_eq!(Some(2i32), row.get::<i32, _>("r#type"));
}
#[test]
fn row_accessors() {
let row = make_row();
assert_eq!(2, row.columns().len());
assert_eq!(2, row.len());
assert_eq!(0, row.result_index());
let cells: Vec<_> = row.cells().collect();
assert_eq!(2, cells.len());
assert_eq!("foo", cells[0].0.name());
let token_row = row.into_token_row();
assert_eq!(2, token_row.len());
}
#[test]
fn row_into_iterator_yields_column_data() {
let row = make_row();
let values: Vec<_> = row.into_iter().collect();
assert_eq!(
vec![ColumnData::I32(Some(1)), ColumnData::I32(Some(2))],
values
);
}
#[test]
fn get_column_data_missing_cell_errors() {
let columns = Arc::new(vec![
Column::new("a".to_string(), ColumnType::Int4),
Column::new("b".to_string(), ColumnType::Int4),
]);
let mut data = TokenRow::new();
data.push(ColumnData::I32(Some(1)));
let row = Row {
columns,
data,
result_index: 0,
};
let err = row.get_column_data(1usize).unwrap_err();
assert!(format!("{}", err).contains("row has no data for column index"));
}
#[test]
fn column_new_and_accessors() {
let column = Column::new("id".to_string(), ColumnType::Int8);
assert_eq!("id", column.name());
assert_eq!(ColumnType::Int8, column.column_type());
}
#[test]
fn column_type_from_fixed_len_type_info() {
use crate::tds::codec::FixedLenType;
let cases = [
(FixedLenType::Int1, ColumnType::Int1),
(FixedLenType::Bit, ColumnType::Bit),
(FixedLenType::Int2, ColumnType::Int2),
(FixedLenType::Int4, ColumnType::Int4),
(FixedLenType::Datetime4, ColumnType::Datetime4),
(FixedLenType::Float4, ColumnType::Float4),
(FixedLenType::Money, ColumnType::Money),
(FixedLenType::Datetime, ColumnType::Datetime),
(FixedLenType::Float8, ColumnType::Float8),
(FixedLenType::Money4, ColumnType::Money4),
(FixedLenType::Int8, ColumnType::Int8),
(FixedLenType::Null, ColumnType::Null),
];
for (flt, expected) in cases {
let ti = TypeInfo::FixedLen(flt);
assert_eq!(ColumnType::from(&ti), expected);
}
}
#[test]
fn column_type_from_var_len_sized_type_info() {
use crate::tds::codec::VarLenType;
use crate::VarLenContext;
let cases = [
(VarLenType::Guid, 16, ColumnType::Guid),
(VarLenType::Intn, 1, ColumnType::Int1),
(VarLenType::Intn, 2, ColumnType::Int2),
(VarLenType::Intn, 4, ColumnType::Int4),
(VarLenType::Intn, 8, ColumnType::Int8),
(VarLenType::Intn, 3, ColumnType::Intn),
(VarLenType::Bitn, 1, ColumnType::Bitn),
(VarLenType::Decimaln, 17, ColumnType::Decimaln),
(VarLenType::Numericn, 17, ColumnType::Numericn),
(VarLenType::Floatn, 4, ColumnType::Float4),
(VarLenType::Floatn, 8, ColumnType::Float8),
(VarLenType::Floatn, 2, ColumnType::Floatn),
(VarLenType::Money, 8, ColumnType::Money),
(VarLenType::Datetimen, 8, ColumnType::Datetimen),
(VarLenType::BigVarBin, 8000, ColumnType::BigVarBin),
(VarLenType::BigVarChar, 8000, ColumnType::BigVarChar),
(VarLenType::BigBinary, 8000, ColumnType::BigBinary),
(VarLenType::BigChar, 8000, ColumnType::BigChar),
(VarLenType::NVarchar, 4000, ColumnType::NVarchar),
(VarLenType::NChar, 4000, ColumnType::NChar),
(VarLenType::Xml, 0, ColumnType::Xml),
(VarLenType::Udt, 0, ColumnType::Udt),
(VarLenType::Text, 0, ColumnType::Text),
(VarLenType::Image, 0, ColumnType::Image),
(VarLenType::NText, 0, ColumnType::NText),
(VarLenType::SSVariant, 0, ColumnType::SSVariant),
];
for (ty, len, expected) in cases {
let ti = TypeInfo::VarLenSized(VarLenContext::new(ty, len, None));
assert_eq!(ColumnType::from(&ti), expected, "{:?} len {}", ty, len);
}
}
#[test]
fn column_type_from_var_len_sized_precision_type_info() {
use crate::tds::codec::VarLenType;
let cases = [
(VarLenType::Guid, ColumnType::Guid),
(VarLenType::Intn, ColumnType::Intn),
(VarLenType::Bitn, ColumnType::Bitn),
(VarLenType::Decimaln, ColumnType::Decimaln),
(VarLenType::Numericn, ColumnType::Numericn),
(VarLenType::Floatn, ColumnType::Floatn),
(VarLenType::Money, ColumnType::Money),
(VarLenType::Datetimen, ColumnType::Datetimen),
(VarLenType::BigVarBin, ColumnType::BigVarBin),
(VarLenType::BigVarChar, ColumnType::BigVarChar),
(VarLenType::BigBinary, ColumnType::BigBinary),
(VarLenType::BigChar, ColumnType::BigChar),
(VarLenType::NVarchar, ColumnType::NVarchar),
(VarLenType::NChar, ColumnType::NChar),
(VarLenType::Xml, ColumnType::Xml),
(VarLenType::Udt, ColumnType::Udt),
(VarLenType::Text, ColumnType::Text),
(VarLenType::Image, ColumnType::Image),
(VarLenType::NText, ColumnType::NText),
(VarLenType::SSVariant, ColumnType::SSVariant),
];
for (ty, expected) in cases {
let ti = TypeInfo::VarLenSizedPrecision {
ty,
size: 38,
precision: 38,
scale: 2,
};
assert_eq!(ColumnType::from(&ti), expected, "{:?}", ty);
}
}
#[test]
fn column_type_from_xml_and_udt_type_info() {
use crate::tds::xml::XmlSchema;
use std::sync::Arc as StdArc;
let ti = TypeInfo::Xml {
schema: None::<StdArc<XmlSchema>>,
size: 0,
};
assert_eq!(ColumnType::from(&ti), ColumnType::Xml);
}
}