use crate::{error::Error, sql_read_bytes::SqlReadBytes, ColumnData};
use bytes::BufMut;
use std::fmt::Display;
const MONEY_SCALE: u32 = 4;
const MONEY_SCALE_FACTOR: f64 = 1e4;
fn put_scaled<B>(
dst: &mut B,
max_len: usize,
scaled: i128,
value: &dyn Display,
length_prefixed: bool,
) -> crate::Result<()>
where
B: BufMut,
{
if max_len == 4 {
if scaled < i32::MIN as i128 || scaled > i32::MAX as i128 {
return Err(Error::BulkInput(
format!(
"money: value {value} is out of range for smallmoney \
(-214_748.3648 ..= 214_748.3647)"
)
.into(),
));
}
if length_prefixed {
dst.put_u8(4);
}
dst.put_i32_le(scaled as i32);
} else {
if scaled < i64::MIN as i128 || scaled > i64::MAX as i128 {
return Err(Error::BulkInput(
format!(
"money: value {value} is out of range for money \
(-922_337_203_685_477.5808 ..= 922_337_203_685_477.5807)"
)
.into(),
));
}
if length_prefixed {
dst.put_u8(8);
}
let scaled = scaled as i64;
dst.put_i32_le((scaled >> 32) as i32);
dst.put_u32_le(scaled as u32);
}
Ok(())
}
pub(crate) fn encode<B>(dst: &mut B, max_len: usize, val: f64) -> crate::Result<()>
where
B: BufMut,
{
encode_inner(dst, max_len, val, true)
}
pub(crate) fn encode_fixed<B>(dst: &mut B, max_len: usize, val: f64) -> crate::Result<()>
where
B: BufMut,
{
encode_inner(dst, max_len, val, false)
}
fn encode_inner<B>(
dst: &mut B,
max_len: usize,
val: f64,
length_prefixed: bool,
) -> crate::Result<()>
where
B: BufMut,
{
if !val.is_finite() {
return Err(Error::BulkInput(
format!("money: value {val} is not finite (NaN/Inf cannot be encoded)").into(),
));
}
let scaled = (val * MONEY_SCALE_FACTOR).round() as i128;
put_scaled(dst, max_len, scaled, &val, length_prefixed)
}
pub(crate) fn encode_numeric<B>(
dst: &mut B,
max_len: usize,
num: &crate::tds::Numeric,
) -> crate::Result<()>
where
B: BufMut,
{
encode_numeric_inner(dst, max_len, num, true)
}
pub(crate) fn encode_numeric_fixed<B>(
dst: &mut B,
max_len: usize,
num: &crate::tds::Numeric,
) -> crate::Result<()>
where
B: BufMut,
{
encode_numeric_inner(dst, max_len, num, false)
}
fn encode_numeric_inner<B>(
dst: &mut B,
max_len: usize,
num: &crate::tds::Numeric,
length_prefixed: bool,
) -> crate::Result<()>
where
B: BufMut,
{
let scale = num.scale() as u32;
let mantissa = num.value();
let scaled: i128 = match scale.cmp(&MONEY_SCALE) {
std::cmp::Ordering::Equal => mantissa,
std::cmp::Ordering::Less => {
let factor = 10i128.pow(MONEY_SCALE - scale);
mantissa.checked_mul(factor).ok_or_else(|| {
Error::BulkInput(
format!("money: value {num} overflows while rescaling to money").into(),
)
})?
}
std::cmp::Ordering::Greater => {
let divisor = 10i128.pow(scale - MONEY_SCALE);
let quotient = mantissa / divisor;
let remainder = (mantissa % divisor).abs();
if remainder * 2 >= divisor {
quotient + mantissa.signum()
} else {
quotient
}
}
};
put_scaled(dst, max_len, scaled, &num, length_prefixed)
}
pub(crate) async fn decode<R>(src: &mut R, len: u8) -> crate::Result<ColumnData<'static>>
where
R: SqlReadBytes + Unpin,
{
let res = match len {
0 => ColumnData::F64(None),
4 => ColumnData::F64(Some(src.read_i32_le().await? as f64 / 1e4)),
8 => ColumnData::F64(Some({
let high = src.read_i32_le().await? as i64;
let low = src.read_u32_le().await? as f64;
((high << 32) as f64 + low) / 1e4
})),
_ => {
return Err(Error::Protocol(
format!("money: length of {} is invalid", len).into(),
))
}
};
Ok(res)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sql_read_bytes::test_utils::IntoSqlReadBytes;
use bytes::{BufMut, BytesMut};
#[tokio::test]
async fn decode_smallmoney_arm() {
let mut buf = BytesMut::new();
buf.put_i32_le(12345);
let data = decode(&mut buf.into_sql_read_bytes(), 4).await.unwrap();
match data {
ColumnData::F64(Some(v)) => assert!((v - 1.2345).abs() < 1e-9, "v={}", v),
other => panic!("expected F64, got {:?}", other),
}
}
#[tokio::test]
async fn decode_money_arm() {
let mut buf = BytesMut::new();
buf.put_i32_le(1); buf.put_u32_le(30000);
let data = decode(&mut buf.into_sql_read_bytes(), 8).await.unwrap();
match data {
ColumnData::F64(Some(v)) => assert!((v - 429499.7296).abs() < 1e-6, "v={}", v),
other => panic!("expected F64, got {:?}", other),
}
}
fn decode_bytes(bytes: &[u8]) -> f64 {
let len = bytes[0];
match len {
4 => i32::from_le_bytes(bytes[1..5].try_into().unwrap()) as f64 / 1e4,
8 => {
let high = i32::from_le_bytes(bytes[1..5].try_into().unwrap()) as i64;
let low = u32::from_le_bytes(bytes[5..9].try_into().unwrap()) as f64;
((high << 32) as f64 + low) / 1e4
}
_ => panic!("invalid length"),
}
}
#[test]
fn encode_smallmoney_roundtrips() {
let mut buf = Vec::new();
encode(&mut buf, 4, 1234.5678).unwrap();
assert_eq!(buf[0], 4);
assert_eq!(buf.len(), 5);
assert_eq!(decode_bytes(&buf), 1234.5678);
}
#[tokio::test]
async fn decode_invalid_length_errors() {
let mut buf = BytesMut::new();
buf.put_u8(0);
let err = decode(&mut buf.into_sql_read_bytes(), 5).await.unwrap_err();
assert!(matches!(err, Error::Protocol(_)), "got {err:?}");
}
#[test]
fn decode_bytes_invalid_length_panics() {
let result = std::panic::catch_unwind(|| decode_bytes(&[2u8, 0, 0, 0, 0]));
assert!(result.is_err());
}
#[test]
fn encode_money_roundtrips() {
for val in [0.0, 1.0, -1.0, 1234.5678, -9999.9999, 92233720368.5477] {
let mut buf = Vec::new();
encode(&mut buf, 8, val).unwrap();
assert_eq!(buf[0], 8);
assert_eq!(buf.len(), 9);
assert!((decode_bytes(&buf) - val).abs() < 1e-3, "val={}", val);
}
}
#[test]
fn encode_nan_is_rejected() {
for max_len in [4usize, 8usize] {
let mut buf = Vec::new();
let err = encode(&mut buf, max_len, f64::NAN).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty(), "nothing must be written on rejection");
}
}
#[test]
fn encode_positive_infinity_is_rejected() {
for max_len in [4usize, 8usize] {
let mut buf = Vec::new();
let err = encode(&mut buf, max_len, f64::INFINITY).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
}
}
#[test]
fn encode_negative_infinity_is_rejected() {
for max_len in [4usize, 8usize] {
let mut buf = Vec::new();
let err = encode(&mut buf, max_len, f64::NEG_INFINITY).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
}
}
#[test]
fn encode_over_max_is_rejected() {
let mut buf = Vec::new();
let err = encode(&mut buf, 4, 214_749.0).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
let mut buf = Vec::new();
let err = encode(&mut buf, 4, -214_749.0).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
let mut buf = Vec::new();
let err = encode(&mut buf, 8, 1e15).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
let mut buf = Vec::new();
let err = encode(&mut buf, 8, -1e15).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
}
#[test]
fn encode_boundaries_are_accepted() {
for val in [214748.3647_f64, -214748.3648_f64] {
let mut buf = Vec::new();
encode(&mut buf, 4, val).unwrap();
assert_eq!(buf[0], 4);
assert!((decode_bytes(&buf) - val).abs() < 1e-3, "val={val}");
}
for val in [922337203685477.5_f64, -922337203685477.5_f64] {
let mut buf = Vec::new();
encode(&mut buf, 8, val).unwrap();
assert_eq!(buf[0], 8);
assert!((decode_bytes(&buf) - val).abs() < 1.0, "val={val}");
}
}
#[test]
fn encode_rejects_float_boundary_just_past_max() {
let mut buf = Vec::new();
let err = encode(&mut buf, 4, 214748.3648).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
let mut buf = Vec::new();
encode(&mut buf, 4, 214748.3647).unwrap();
assert_eq!(decode_bytes_scaled(&buf), i32::MAX as i64);
let mut buf = Vec::new();
let err = encode(&mut buf, 8, 922337203685477.6).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
}
fn decode_bytes_scaled(bytes: &[u8]) -> i64 {
match bytes[0] {
4 => i32::from_le_bytes(bytes[1..5].try_into().unwrap()) as i64,
8 => {
let high = i32::from_le_bytes(bytes[1..5].try_into().unwrap()) as i64;
let low = u32::from_le_bytes(bytes[5..9].try_into().unwrap()) as i64;
(high << 32) | low
}
_ => panic!("invalid length"),
}
}
#[test]
fn encode_numeric_large_value_is_exact() {
let scaled: i128 = 123_456_789_012_345_678;
let num = crate::tds::Numeric::new_with_scale(scaled, 4);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(buf[0], 8);
assert_eq!(decode_bytes_scaled(&buf) as i128, scaled);
let via_f64 = (f64::from(num) * 1e4).round() as i128;
assert_ne!(via_f64, scaled, "f64 path unexpectedly stayed exact");
}
#[test]
fn encode_numeric_scale_less_than_four() {
let num = crate::tds::Numeric::new_with_scale(1234, 2);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(decode_bytes_scaled(&buf), 123_400);
}
#[test]
fn encode_numeric_scale_greater_than_four_rounds_half_away() {
let num = crate::tds::Numeric::new_with_scale(123_455, 5);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(decode_bytes_scaled(&buf), 12_346);
let num = crate::tds::Numeric::new_with_scale(-123_455, 5);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(decode_bytes_scaled(&buf), -12_346);
let num = crate::tds::Numeric::new_with_scale(123_454, 5);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(decode_bytes_scaled(&buf), 12_345);
}
#[test]
fn encode_numeric_out_of_range_is_rejected() {
let num = crate::tds::Numeric::new_with_scale(3_000_000_000, 4);
let mut buf = Vec::new();
let err = encode_numeric(&mut buf, 4, &num).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
let num = crate::tds::Numeric::new_with_scale(10_000_000_000_000_000_000, 4);
let mut buf = Vec::new();
let err = encode_numeric(&mut buf, 8, &num).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
let num = crate::tds::Numeric::new_with_scale(i128::MAX, 0);
let mut buf = Vec::new();
let err = encode_numeric(&mut buf, 8, &num).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
}
#[test]
fn encode_fixed_has_no_length_prefix() {
let mut buf = Vec::new();
encode_fixed(&mut buf, 4, 1234.5678).unwrap();
assert_eq!(buf.len(), 4, "smallmoney fixed len must be exactly 4 bytes");
let scaled = i32::from_le_bytes(buf[..4].try_into().unwrap());
assert_eq!(scaled, 12_345_678);
let mut buf = Vec::new();
encode_fixed(&mut buf, 8, 1234.5678).unwrap();
assert_eq!(buf.len(), 8, "money fixed len must be exactly 8 bytes");
let high = i32::from_le_bytes(buf[0..4].try_into().unwrap()) as i64;
let low = u32::from_le_bytes(buf[4..8].try_into().unwrap()) as i64;
assert_eq!((high << 32) | low, 12_345_678);
}
#[test]
fn encode_numeric_fixed_has_no_length_prefix() {
let num = crate::tds::Numeric::new_with_scale(12_345_678, 4);
let mut buf = Vec::new();
encode_numeric_fixed(&mut buf, 4, &num).unwrap();
assert_eq!(buf.len(), 4);
assert_eq!(i32::from_le_bytes(buf[..4].try_into().unwrap()), 12_345_678);
let mut buf = Vec::new();
encode_numeric_fixed(&mut buf, 8, &num).unwrap();
assert_eq!(buf.len(), 8);
let high = i32::from_le_bytes(buf[0..4].try_into().unwrap()) as i64;
let low = u32::from_le_bytes(buf[4..8].try_into().unwrap()) as i64;
assert_eq!((high << 32) | low, 12_345_678);
}
#[test]
fn encode_fixed_out_of_range_is_rejected() {
let mut buf = Vec::new();
let err = encode_fixed(&mut buf, 4, 214_749.0).unwrap_err();
assert!(matches!(err, Error::BulkInput(_)), "got {err:?}");
assert!(buf.is_empty());
}
#[test]
fn encode_numeric_boundaries_are_exact() {
for scaled in [i32::MAX as i128, i32::MIN as i128] {
let num = crate::tds::Numeric::new_with_scale(scaled, 4);
let mut buf = Vec::new();
encode_numeric(&mut buf, 4, &num).unwrap();
assert_eq!(buf[0], 4);
assert_eq!(decode_bytes_scaled(&buf) as i128, scaled);
}
for scaled in [i64::MAX as i128, i64::MIN as i128] {
let num = crate::tds::Numeric::new_with_scale(scaled, 4);
let mut buf = Vec::new();
encode_numeric(&mut buf, 8, &num).unwrap();
assert_eq!(buf[0], 8);
assert_eq!(decode_bytes_scaled(&buf) as i128, scaled);
}
}
}