use crate::{Result, error};
use std::borrow::Cow;
#[derive(Debug)]
pub enum DecimalView<'a> {
String { value: &'a str },
Scaled { scale: u8, value: Cow<'a, [u8]> },
}
impl<'a> DecimalView<'a> {
pub fn try_new_scaled<T>(scale: u32, value: T) -> Result<Self>
where
T: Into<Cow<'a, [u8]>>,
{
if scale > 76 {
return Err(error::fmt!(
InvalidDecimal,
"QuestDB ILP does not support decimal scale greater than 76, got {}",
scale
));
}
let value: Cow<'a, [u8]> = value.into();
if value.len() > 32_usize {
return Err(error::fmt!(
InvalidDecimal,
"QuestDB ILP does not support decimal longer than 32 bytes, got {}",
value.len()
));
}
Ok(DecimalView::Scaled {
scale: scale as u8,
value,
})
}
pub fn try_new_string(value: &'a str) -> Result<Self> {
for b in value.chars() {
match b {
'0'..='9'
| '.'
| '-'
| '+'
| 'e'
| 'E'
| 'N'
| 'a'
| 'I'
| 'n'
| 'f'
| 'i'
| 't'
| 'y' => {}
_ => {
return Err(error::fmt!(
InvalidDecimal,
"Decimal string contains ILP reserved character {:?}",
b
));
}
}
}
Ok(DecimalView::String { value })
}
pub(crate) fn serialize(&self, out: &mut Vec<u8>) {
match self {
DecimalView::String { value } => Self::serialize_string(value, out),
DecimalView::Scaled { scale, value } => {
Self::serialize_scaled(*scale, value.as_ref(), out)
}
}
}
fn serialize_string(value: &str, out: &mut Vec<u8>) {
out.reserve(value.len() + 1);
out.extend_from_slice(value.as_bytes());
out.push(b'd');
}
fn serialize_scaled(scale: u8, value: &[u8], out: &mut Vec<u8>) {
out.push(b'=');
out.push(crate::ingress::DECIMAL_BINARY_FORMAT_TYPE);
out.push(scale);
out.push(value.len() as u8);
out.extend_from_slice(value);
}
}
impl<'a> TryInto<DecimalView<'a>> for &'a str {
type Error = crate::Error;
fn try_into(self) -> Result<DecimalView<'a>> {
DecimalView::try_new_string(self)
}
}
#[cfg(feature = "rust_decimal")]
impl<'a> TryInto<DecimalView<'a>> for &'a rust_decimal::Decimal {
type Error = crate::Error;
fn try_into(self) -> Result<DecimalView<'a>> {
let raw = self.mantissa().to_be_bytes();
let bytes = trim_leading_sign_bytes(&raw);
DecimalView::try_new_scaled(self.scale(), bytes)
}
}
#[cfg(feature = "bigdecimal")]
impl<'a> TryInto<DecimalView<'a>> for &'a bigdecimal::BigDecimal {
type Error = crate::Error;
fn try_into(self) -> Result<DecimalView<'a>> {
let (unscaled, mut scale) = self.as_bigint_and_scale();
let bytes = if scale < 0 {
use bigdecimal::num_bigint;
let unscaled =
unscaled.into_owned() * num_bigint::BigInt::from(10).pow((-scale) as u32);
scale = 0;
unscaled.to_signed_bytes_be()
} else {
unscaled.to_signed_bytes_be()
};
let bytes = trim_leading_sign_bytes(&bytes);
DecimalView::try_new_scaled(scale as u32, bytes)
}
}
#[cfg(any(feature = "rust_decimal", feature = "bigdecimal"))]
fn trim_leading_sign_bytes(bytes: &[u8]) -> Vec<u8> {
if bytes.is_empty() {
return vec![0];
}
let negative = bytes[0] & 0x80 != 0;
let mut keep_from = 0usize;
while keep_from < bytes.len() - 1 {
let current = bytes[keep_from];
let next = bytes[keep_from + 1];
let should_trim = if negative {
current == 0xFF && (next & 0x80) == 0x80
} else {
current == 0x00 && (next & 0x80) == 0x00
};
if should_trim {
keep_from += 1;
} else {
break;
}
}
bytes[keep_from..].to_vec()
}