use std::mem::align_of;
use crate::constant::{ColumnFlags, ColumnType};
use crate::protocol::command::ColumnDefinitionTail;
use crate::raw::parse_value;
use crate::test_macros::{check, check_eq};
use crate::value::{NullBitmap, Time8, Time12, Timestamp4, Timestamp7, Timestamp11, Value};
use zerocopy::FromBytes;
fn make_col_tail(
column_type: ColumnType,
flags: ColumnFlags,
) -> crate::error::Result<ColumnDefinitionTail> {
let mut bytes = [0u8; 12];
bytes[0..2].copy_from_slice(&33u16.to_le_bytes()); bytes[2..6].copy_from_slice(&255u32.to_le_bytes()); bytes[6] = column_type as u8; bytes[7..9].copy_from_slice(&flags.bits().to_le_bytes()); bytes[9] = 0; bytes[10..12].copy_from_slice(&0u16.to_le_bytes()); Ok(*ColumnDefinitionTail::ref_from_bytes(&bytes)?)
}
#[test]
fn value_parse_signed_integers() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_TINY, ColumnFlags::empty())?;
let data = [214u8]; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::SignedInt(-42)));
check_eq!(rest.len(), 0);
let col2 = make_col_tail(ColumnType::MYSQL_TYPE_SHORT, ColumnFlags::empty())?;
let data2 = [0x18, 0xFC]; let (value2, rest2) = parse_value::<Value>(&col2, false, &data2)?;
check!(matches!(value2, Value::SignedInt(-1000)));
check_eq!(rest2.len(), 0);
let col3 = make_col_tail(ColumnType::MYSQL_TYPE_LONG, ColumnFlags::empty())?;
let data3 = [0x60, 0x79, 0xFE, 0xFF]; let (value3, rest3) = parse_value::<Value>(&col3, false, &data3)?;
check!(matches!(value3, Value::SignedInt(-100000)));
check_eq!(rest3.len(), 0);
Ok(())
}
#[test]
fn value_parse_unsigned_integers() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_TINY, ColumnFlags::UNSIGNED_FLAG)?;
let data = [200_u8];
let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::UnsignedInt(200)));
check_eq!(rest.len(), 0);
let col2 = make_col_tail(ColumnType::MYSQL_TYPE_LONGLONG, ColumnFlags::UNSIGNED_FLAG)?;
let data2 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F]; let (value2, rest2) = parse_value::<Value>(&col2, false, &data2)?;
check!(matches!(value2, Value::UnsignedInt(0x7FFF_FFFF_FFFF_FFFF)));
check_eq!(rest2.len(), 0);
Ok(())
}
#[test]
fn value_parse_float_double() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_FLOAT, ColumnFlags::empty())?;
let data = 3.12f32.to_le_bytes();
let (value, rest) = parse_value::<Value>(&col, false, &data)?;
let Value::Float(f) = value else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Float value"
)));
};
let close = (f - 3.12).abs() < 0.001;
check!(close);
check_eq!(rest.len(), 0);
let col2 = make_col_tail(ColumnType::MYSQL_TYPE_DOUBLE, ColumnFlags::empty())?;
let data2 = std::f64::consts::PI.to_le_bytes();
let (value2, rest2) = parse_value::<Value>(&col2, false, &data2)?;
let Value::Double(d) = value2 else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Double value"
)));
};
let close2 = (d - std::f64::consts::PI).abs() < 0.0000001;
check!(close2);
check_eq!(rest2.len(), 0);
Ok(())
}
#[test]
fn value_parse_datetime() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_DATETIME, ColumnFlags::empty())?;
let data = [0_u8]; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::Datetime0));
check_eq!(rest.len(), 0);
let mut data2 = vec![4u8]; data2.extend_from_slice(&2024u16.to_le_bytes()); data2.push(12); data2.push(25); let (value2, rest2) = parse_value::<Value>(&col, false, &data2)?;
let Value::Datetime4(ts) = value2 else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Datetime4 value"
)));
};
check_eq!(ts.year(), 2024);
check_eq!(ts.month, 12);
check_eq!(ts.day, 25);
check_eq!(rest2.len(), 0);
let mut data3 = vec![7u8]; data3.extend_from_slice(&2024u16.to_le_bytes()); data3.push(12); data3.push(25); data3.push(15); data3.push(30); data3.push(45); let (value3, rest3) = parse_value::<Value>(&col, false, &data3)?;
let Value::Datetime7(ts2) = value3 else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Datetime7 value"
)));
};
check_eq!(ts2.year(), 2024);
check_eq!(ts2.month, 12);
check_eq!(ts2.day, 25);
check_eq!(ts2.hour, 15);
check_eq!(ts2.minute, 30);
check_eq!(ts2.second, 45);
check_eq!(rest3.len(), 0);
Ok(())
}
#[test]
fn value_parse_date() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_DATE, ColumnFlags::empty())?;
let data = [0_u8]; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::Date0));
check_eq!(rest.len(), 0);
let mut data2 = vec![4u8]; data2.extend_from_slice(&2024u16.to_le_bytes()); data2.push(12); data2.push(25); let (value2, rest2) = parse_value::<Value>(&col, false, &data2)?;
let Value::Date4(ts) = value2 else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Date4 value, got {:?}",
value2
)));
};
check_eq!(ts.year(), 2024);
check_eq!(ts.month, 12);
check_eq!(ts.day, 25);
check_eq!(rest2.len(), 0);
Ok(())
}
#[test]
fn value_parse_time() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_TIME, ColumnFlags::empty())?;
let data = [0_u8]; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::Time0));
check_eq!(rest.len(), 0);
let mut data2 = vec![8u8]; data2.push(1); data2.extend_from_slice(&1u32.to_le_bytes()); data2.push(12); data2.push(30); data2.push(45); let (value2, rest2) = parse_value::<Value>(&col, false, &data2)?;
let Value::Time8(time) = value2 else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Time8 value"
)));
};
check!(time.is_negative());
check_eq!(time.days(), 1);
check_eq!(time.hour, 12);
check_eq!(time.minute, 30);
check_eq!(time.second, 45);
check_eq!(rest2.len(), 0);
Ok(())
}
#[test]
fn value_parse_string() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_VAR_STRING, ColumnFlags::empty())?;
let mut data = vec![5u8]; data.extend_from_slice(b"Hello");
let (value, rest) = parse_value::<Value>(&col, false, &data)?;
let Value::Byte(bytes) = value else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Byte value"
)));
};
check_eq!(bytes, b"Hello");
check_eq!(rest.len(), 0);
Ok(())
}
#[test]
fn value_parse_blob() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_BLOB, ColumnFlags::empty())?;
let mut data = vec![4u8]; data.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let (value, rest) = parse_value::<Value>(&col, false, &data)?;
let Value::Byte(bytes) = value else {
return Err(crate::error::Error::LibraryBug(crate::error::eyre!(
"Expected Byte value"
)));
};
check_eq!(bytes, &[0xDE, 0xAD, 0xBE, 0xEF]);
check_eq!(rest.len(), 0);
Ok(())
}
#[test]
fn value_parse_null() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_NULL, ColumnFlags::empty())?;
let data = []; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::Null));
check_eq!(rest.len(), 0);
Ok(())
}
#[test]
fn value_parse_with_remaining_data() -> crate::error::Result<()> {
let col = make_col_tail(ColumnType::MYSQL_TYPE_TINY, ColumnFlags::UNSIGNED_FLAG)?;
let data = [42u8, 0xFF, 0xFF]; let (value, rest) = parse_value::<Value>(&col, false, &data)?;
check!(matches!(value, Value::UnsignedInt(42)));
check_eq!(rest, &[0xFF, 0xFF]);
Ok(())
}
#[test]
fn null_bitmap_result_set() {
let bitmap = [0b00000100, 0b00010000];
let null_bitmap = NullBitmap::for_result_set(&bitmap);
assert!(null_bitmap.is_null(0)); assert!(!null_bitmap.is_null(1)); assert!(!null_bitmap.is_null(2)); assert!(null_bitmap.is_null(10)); }
#[test]
fn null_bitmap_parameters() {
let bitmap = [0b00000101];
let null_bitmap = NullBitmap::for_parameters(&bitmap);
assert!(null_bitmap.is_null(0)); assert!(!null_bitmap.is_null(1)); assert!(null_bitmap.is_null(2)); assert!(!null_bitmap.is_null(3)); }
#[test]
fn zerocopy_types_have_alignment_of_1() {
assert_eq!(align_of::<Timestamp4>(), 1);
assert_eq!(align_of::<Timestamp7>(), 1);
assert_eq!(align_of::<Timestamp11>(), 1);
assert_eq!(align_of::<Time8>(), 1);
assert_eq!(align_of::<Time12>(), 1);
}