use arrow::{array::ArrowNativeTypeOp, datatypes::i256};
use bytes::{BufMut, BytesMut};
use itertools::Itertools;
use std::io::{Cursor, Read};
fn read_two_bytes(cursor: &mut Cursor<&[u8]>) -> std::io::Result<[u8; 2]> {
let mut result = [0; 2];
cursor.read_exact(&mut result)?;
Ok(result)
}
pub fn from_sql(raw: &[u8]) -> std::io::Result<String> {
let mut raw = Cursor::new(raw);
let num_groups = u16::from_be_bytes(read_two_bytes(&mut raw)?);
let weight_first = i16::from_be_bytes(read_two_bytes(&mut raw)?);
let sign = u16::from_be_bytes(read_two_bytes(&mut raw)?);
let scale = i16::from_be_bytes(read_two_bytes(&mut raw)?);
let negate = match sign {
0x0000 => false,
0x4000 => true,
0xD000 => return Ok("Infinity".into()),
0xF000 => return Ok("-Infinity".into()),
0xC000 => return Ok("NaN".into()),
_ => panic!("invalid sign value: {:x}", sign),
};
let mut buf = String::new();
let mut prev_remainder = 0;
for _ in 0..num_groups as usize {
let group = u16::from_be_bytes(read_two_bytes(&mut raw)?);
let number = group as u32 + prev_remainder * 1_0000;
buf += &(format!("{:0>4}", (number / 10)));
prev_remainder = number % 10;
}
buf += &prev_remainder.to_string();
let dot = (weight_first * 4 + 5) as usize;
let frac_end = dot + scale as usize;
if frac_end > buf.len() {
buf += &"0".repeat(frac_end - buf.len());
}
let (first_non_zero, _) = buf
.chars()
.find_position(|x| *x != '0')
.unwrap_or((dot - 1, '0'));
let int_start = first_non_zero.min(dot - 1);
let mut res = String::new();
if negate {
res += "-";
}
res += &buf[int_start..dot];
if scale > 0 {
res += ".";
res += &buf[dot..frac_end];
}
Ok(res)
}
pub fn i128_to_sql(data: i128, scale: i8, out: &mut BytesMut) {
let neg = data < 0;
let mut data = if neg { data.wrapping_neg() } else { data } as u128;
let mut groups = Vec::with_capacity(8);
let scale_offset = scale % 4;
let mut weight = -(scale as i16) / 4 - 1;
if scale_offset > 0 {
let multiplier = 10u128.pow(scale_offset as u32);
groups.push((data % multiplier) as u16 * 10u16.pow(4 - scale_offset as u32));
data /= multiplier;
}
while data != 0 {
groups.push((data % 10000) as u16);
data /= 10000;
weight += 1;
}
groups.reverse();
let num_groups = groups.len();
out.reserve(8 + num_groups * 2);
out.put_i16(num_groups as i16);
out.put_i16(weight);
out.put_i16(if neg { 0x4000 } else { 0x0000 });
out.put_i16(scale as i16);
for group in groups {
out.put_u16(group);
}
}
pub fn i256_to_sql(data: i256, scale: i8, out: &mut BytesMut) {
let neg = data.is_negative();
let mut data = if neg { data.wrapping_neg() } else { data };
let mut groups = Vec::with_capacity(8);
let scale_offset = scale % 4;
let mut weight = -(scale as i16) / 4 - 1;
if scale_offset > 0 {
let multiplier = i256::from_i128(10)
.pow_checked(scale_offset as u32)
.unwrap();
let group = (data.wrapping_rem(multiplier)).as_i128() as u16;
groups.push(group * 10u16.pow(4 - scale_offset as u32));
data = data.div_wrapping(multiplier);
}
while !data.is_zero() {
let multiplier = i256::from_i128(10000);
let group = (data.wrapping_rem(multiplier)).as_i128() as u16;
groups.push(group);
data = data.wrapping_div(multiplier);
weight += 1;
}
groups.reverse();
let num_groups = groups.len();
if num_groups > 100 {
panic!();
}
out.reserve(8 + num_groups * 2);
out.put_i16(num_groups as i16);
out.put_i16(weight);
out.put_i16(if neg { 0x4000 } else { 0x0000 });
out.put_i16(scale as i16);
for group in groups {
out.put_u16(group);
}
}
#[test]
fn test_from_sql_01() {
let raw = [
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0F, 0x6E, 0x04, 0xD2, 0x15, 0xE0,
];
let res = (3950123456i128, 6);
assert_eq!(&from_sql(&raw).unwrap(), "3950.123456");
let mut bytes = BytesMut::new();
i128_to_sql(res.0, res.1 as i8, &mut bytes);
assert_eq!(&raw, &bytes[..]);
}
#[test]
fn test_from_sql_02() {
let raw = [
0x00, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x06, 0x0F, 0x6E, 0x04, 0xD2, 0x15, 0xE0,
];
let res = (-3950123456i128, 6);
assert_eq!(&from_sql(&raw).unwrap(), "-3950.123456");
let mut bytes = BytesMut::new();
i128_to_sql(res.0, res.1 as i8, &mut bytes);
assert_eq!(&raw, &bytes[..]);
}
#[test]
fn test_from_sql_03() {
let raw = [
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x6E, 0x04, 0xD2, 0x15, 0xE0,
];
let res = (39501234560i128, 7);
assert_eq!(&from_sql(&raw).unwrap(), "3950.1234560");
let mut bytes = BytesMut::new();
i128_to_sql(res.0, res.1 as i8, &mut bytes);
assert_eq!(&raw, &bytes[..]);
}