use crate::core::TdsResult;
use crate::datatypes::decoder::DecimalParts;
use crate::datatypes::sql_json::SqlJson;
use crate::datatypes::sql_string::SqlString;
use crate::datatypes::sql_vector::SqlVector;
use crate::error::Error;
use core::fmt;
use std::fmt::Debug;
use uuid::Uuid;
#[derive(Debug, PartialOrd, PartialEq, Clone)]
pub struct SqlXml {
pub bytes: Vec<u8>,
}
impl SqlXml {
pub fn as_string(&self) -> String {
let mut u16_buffer = Vec::with_capacity(self.bytes.len() / 2);
self.bytes
.chunks(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.for_each(|item| u16_buffer.push(item));
String::from_utf16(&u16_buffer).unwrap()
}
pub fn has_bom(&self) -> bool {
self.bytes.len() >= 2 && (self.bytes[0] == 0xFF && self.bytes[1] == 0xFE)
}
}
impl From<String> for SqlXml {
fn from(input: String) -> SqlXml {
let mut bytes = Vec::with_capacity(input.len() * 2);
input
.encode_utf16()
.for_each(|item| bytes.extend_from_slice(&item.to_le_bytes()));
SqlXml { bytes }
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum ColumnValues {
TinyInt(u8),
SmallInt(i16),
Int(i32),
BigInt(i64),
Real(f32),
Float(f64),
Decimal(DecimalParts),
Numeric(DecimalParts),
Bit(bool),
String(SqlString),
DateTime(SqlDateTime),
Date(SqlDate),
Time(SqlTime),
DateTime2(SqlDateTime2),
DateTimeOffset(SqlDateTimeOffset),
SmallDateTime(SqlSmallDateTime),
SmallMoney(SqlSmallMoney),
Money(SqlMoney),
Bytes(Vec<u8>),
Xml(SqlXml),
Null,
Uuid(Uuid),
Json(SqlJson),
Vector(SqlVector),
}
pub const DEFAULT_VARTIME_SCALE: u8 = 7;
#[derive(Debug, PartialEq, Clone)]
pub struct SqlTime {
pub time_nanoseconds: u64,
pub scale: u8,
}
impl SqlTime {
pub(crate) fn get_scale(&self) -> u8 {
self.scale
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct SqlDateTime2 {
pub days: u32,
pub time: SqlTime,
}
#[derive(Debug, PartialEq, Clone)]
pub struct SqlDateTimeOffset {
pub datetime2: SqlDateTime2,
pub offset: i16,
}
#[derive(PartialEq, Clone, Debug)]
pub struct SqlSmallMoney {
pub int_val: i32,
}
impl From<i32> for SqlSmallMoney {
fn from(value: i32) -> Self {
SqlSmallMoney { int_val: value }
}
}
#[derive(PartialEq, Clone)]
pub struct SqlMoney {
pub lsb_part: i32, pub msb_part: i32, }
impl Debug for SqlMoney {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Money value: {:?}, int_part_1: {:?}, int_part_2: {:?}",
TdsResult::<f64>::from(self).unwrap(),
self.lsb_part,
self.msb_part
)
}
}
impl From<(i32, i32)> for SqlMoney {
fn from(value: (i32, i32)) -> Self {
SqlMoney {
lsb_part: value.0,
msb_part: value.1,
}
}
}
impl From<i32> for SqlMoney {
fn from(value: i32) -> Self {
SqlMoney {
lsb_part: value,
msb_part: 0,
}
}
}
impl From<&SqlMoney> for TdsResult<f64> {
fn from(value: &SqlMoney) -> Self {
let lsb = value.lsb_part;
let msb = value.msb_part;
let lsb_in_i64 = (lsb as i64) & 0x00000000FFFFFFFF;
let money_val = lsb_in_i64 | ((msb as i64) << 32);
Ok((money_val as f64) / 10000.0000)
}
}
impl From<&SqlMoney> for TdsResult<f32> {
fn from(value: &SqlMoney) -> Self {
let scaled_value = (value.lsb_part as f64) / 10000.0000; Ok(scaled_value as f32) }
}
#[derive(Debug, PartialEq, Clone)]
pub struct SqlSmallDateTime {
pub days: u16,
pub time: u16,
}
#[derive(Debug, PartialEq, Clone)]
pub struct SqlDateTime {
pub days: i32,
pub time: u32,
}
#[derive(Debug, PartialEq, Clone)]
pub struct SqlDate {
days_since_01_01_0001: u32,
}
impl SqlDate {
const MIN_DAYS: u32 = 0; const MAX_DAYS: u32 = 3_652_058;
pub fn create(days: u32) -> TdsResult<SqlDate> {
if days <= Self::MAX_DAYS {
Ok(SqlDate {
days_since_01_01_0001: days,
})
} else {
Err(Error::UsageError(format!(
"Date value {} is out of range for DATE column. Valid range: {} to {}",
days,
Self::MIN_DAYS,
Self::MAX_DAYS
)))
}
}
pub(crate) fn unchecked_create(days: u32) -> SqlDate {
SqlDate {
days_since_01_01_0001: days,
}
}
pub fn get_days(&self) -> u32 {
self.days_since_01_01_0001
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sql_xml_from_string() {
let xml_str = "<root><item>test</item></root>".to_string();
let xml = SqlXml::from(xml_str.clone());
assert_eq!(xml.as_string(), xml_str);
}
#[test]
fn test_sql_xml_has_bom() {
let xml_with_bom = SqlXml {
bytes: vec![0xFF, 0xFE, 0x3C, 0x00],
};
assert!(xml_with_bom.has_bom());
let xml_without_bom = SqlXml {
bytes: vec![0x3C, 0x00],
};
assert!(!xml_without_bom.has_bom());
}
#[test]
fn test_sql_xml_empty() {
let xml = SqlXml { bytes: Vec::new() };
assert!(!xml.has_bom());
}
#[test]
fn test_sql_time_get_scale() {
let time = SqlTime {
time_nanoseconds: 123456789,
scale: 5,
};
assert_eq!(time.get_scale(), 5);
}
#[test]
fn test_sql_datetime2_creation() {
let time = SqlTime {
time_nanoseconds: 1000000,
scale: 7,
};
let datetime2 = SqlDateTime2 {
days: 18000,
time: time.clone(),
};
assert_eq!(datetime2.days, 18000);
assert_eq!(datetime2.time.scale, 7);
}
#[test]
fn test_sql_datetimeoffset_creation() {
let time = SqlTime {
time_nanoseconds: 5000000,
scale: 7,
};
let datetime2 = SqlDateTime2 { days: 20000, time };
let dto = SqlDateTimeOffset {
datetime2,
offset: -300,
};
assert_eq!(dto.offset, -300);
assert_eq!(dto.datetime2.days, 20000);
}
#[test]
fn test_sql_small_money_from_i32() {
let money = SqlSmallMoney::from(100000);
assert_eq!(money.int_val, 100000);
}
#[test]
fn test_sql_money_from_i32() {
let money = SqlMoney::from(100000);
assert_eq!(money.lsb_part, 100000);
assert_eq!(money.msb_part, 0);
}
#[test]
fn test_sql_money_from_tuple() {
let money = SqlMoney::from((100000, 50000));
assert_eq!(money.lsb_part, 100000);
assert_eq!(money.msb_part, 50000);
}
#[test]
fn test_sql_money_to_f64() {
let money = SqlMoney::from(100000);
let value: TdsResult<f64> = (&money).into();
assert!(value.is_ok());
assert_eq!(value.unwrap(), 10.0);
}
#[test]
fn test_sql_money_to_f32() {
let money = SqlMoney::from(50000);
let value: TdsResult<f32> = (&money).into();
assert!(value.is_ok());
assert_eq!(value.unwrap(), 5.0);
}
#[test]
fn test_sql_small_datetime_creation() {
let sdt = SqlSmallDateTime {
days: 365,
time: 720,
};
assert_eq!(sdt.days, 365);
assert_eq!(sdt.time, 720);
}
#[test]
fn test_sql_datetime_creation() {
let dt = SqlDateTime {
days: 365,
time: 12345,
};
assert_eq!(dt.days, 365);
assert_eq!(dt.time, 12345);
}
#[test]
fn test_sql_date_create_valid() {
let result = SqlDate::create(100000);
assert!(result.is_ok());
let date = result.unwrap();
assert_eq!(date.get_days(), 100000);
}
#[test]
fn test_sql_date_create_max_valid() {
let result = SqlDate::create(3_652_058);
assert!(result.is_ok());
}
#[test]
fn test_sql_date_create_invalid() {
let result = SqlDate::create(3_652_059);
assert!(result.is_err());
let result_hex = SqlDate::create(0x1000000);
assert!(result_hex.is_err());
}
#[test]
fn test_sql_date_unchecked_create() {
let date = SqlDate::unchecked_create(200000);
assert_eq!(date.get_days(), 200000);
}
#[test]
fn test_column_values_tinyint() {
let val = ColumnValues::TinyInt(255);
assert!(matches!(val, ColumnValues::TinyInt(255)));
}
#[test]
fn test_column_values_smallint() {
let val = ColumnValues::SmallInt(-1000);
assert!(matches!(val, ColumnValues::SmallInt(-1000)));
}
#[test]
fn test_column_values_int() {
let val = ColumnValues::Int(123456);
assert!(matches!(val, ColumnValues::Int(123456)));
}
#[test]
fn test_column_values_bigint() {
let val = ColumnValues::BigInt(9223372036854775807);
assert!(matches!(val, ColumnValues::BigInt(_)));
}
#[test]
fn test_column_values_real() {
let val = ColumnValues::Real(2.5);
assert!(matches!(val, ColumnValues::Real(_)));
}
#[test]
fn test_column_values_float() {
let val = ColumnValues::Float(2.5);
assert!(matches!(val, ColumnValues::Float(_)));
}
#[test]
fn test_column_values_bit() {
let val = ColumnValues::Bit(true);
assert!(matches!(val, ColumnValues::Bit(true)));
}
#[test]
fn test_column_values_null() {
let val = ColumnValues::Null;
assert!(matches!(val, ColumnValues::Null));
}
#[test]
fn test_column_values_bytes() {
let val = ColumnValues::Bytes(vec![1, 2, 3, 4]);
assert!(matches!(val, ColumnValues::Bytes(_)));
}
#[test]
fn test_column_values_uuid() {
let uuid = Uuid::nil();
let val = ColumnValues::Uuid(uuid);
assert!(matches!(val, ColumnValues::Uuid(_)));
}
#[test]
fn test_column_values_clone() {
let val = ColumnValues::Int(42);
let cloned = val.clone();
assert_eq!(val, cloned);
}
#[test]
fn test_sql_xml_clone() {
let xml = SqlXml {
bytes: vec![0x3C, 0x00, 0x3E, 0x00],
};
let cloned = xml.clone();
assert_eq!(xml, cloned);
}
#[test]
fn test_sql_date_min_boundary() {
let result = SqlDate::create(0);
assert!(result.is_ok());
assert_eq!(result.unwrap().get_days(), 0);
}
#[test]
fn test_sql_date_max_boundary() {
let result = SqlDate::create(3_652_058);
assert!(result.is_ok());
assert_eq!(result.unwrap().get_days(), 3_652_058);
}
#[test]
fn test_sql_date_above_max() {
let result = SqlDate::create(3_652_059);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("out of range"));
}
#[test]
fn test_sql_date_far_future() {
let result = SqlDate::create(16_777_215);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("out of range"));
}
#[test]
fn test_sql_date_mid_range() {
let result = SqlDate::create(730_485);
assert!(result.is_ok());
assert_eq!(result.unwrap().get_days(), 730_485);
}
}