use crate::coding::{checked_count, checked_decode_count};
use crate::error::{EncodingError, Result};
use crate::unified::{ColumnDecoder, ColumnEncoder, DecodedColumn, EncodedBlock, EncodingType};
pub(crate) const FORM_NARROW: u8 = 0;
pub(crate) const FORM_WIDE: u8 = 1;
#[inline]
fn zigzag_encode_i128(n: i128) -> u128 {
((n << 1) ^ (n >> 127)) as u128
}
#[inline]
fn zigzag_decode_i128(n: u128) -> i128 {
((n >> 1) as i128) ^ -((n & 1) as i128)
}
#[inline]
fn varint_encode_u128(mut value: u128, out: &mut Vec<u8>) {
while value >= 0x80 {
out.push((value as u8) | 0x80);
value >>= 7;
}
out.push(value as u8);
}
fn varint_decode_u128(bytes: &[u8]) -> Result<(u128, usize)> {
let mut value: u128 = 0;
let mut shift = 0u32;
for (i, &byte) in bytes.iter().enumerate() {
if shift > 126 {
return Err(EncodingError::CorruptData {
detail: "decimal varint exceeds 128 bits".to_string(),
});
}
value |= u128::from(byte & 0x7f) << shift;
if byte & 0x80 == 0 {
return Ok((value, i + 1));
}
shift += 7;
}
Err(EncodingError::CorruptData {
detail: "decimal varint truncated".to_string(),
})
}
#[derive(Debug, Clone, Copy)]
pub struct DecimalEncoder;
impl DecimalEncoder {
pub fn encode(values: &[i128]) -> Result<Vec<u8>> {
if values.is_empty() {
return Err(EncodingError::EmptyInput {
context: "decimal encoder",
});
}
let narrow = values.iter().all(|&v| i64::try_from(v).is_ok());
if narrow {
let as_i64: Vec<i64> = values.iter().map(|&v| v as i64).collect();
let inner = ColumnEncoder::encode_i64(&as_i64)?;
let mut out = Vec::with_capacity(2 + inner.payload.len());
out.push(FORM_NARROW);
out.push(inner.encoding.tag());
out.extend_from_slice(&inner.payload);
return Ok(out);
}
let count = checked_count(values.len())?;
let mut out = Vec::with_capacity(1 + 4 + 16 + values.len() * 4);
out.push(FORM_WIDE);
out.extend_from_slice(&count.to_le_bytes());
out.extend_from_slice(&values[0].to_le_bytes());
for window in values.windows(2) {
let delta = window[1].wrapping_sub(window[0]);
varint_encode_u128(zigzag_encode_i128(delta), &mut out);
}
Ok(out)
}
}
#[derive(Debug, Clone, Copy)]
pub struct DecimalDecoder;
impl DecimalDecoder {
pub fn decode(payload: &[u8]) -> Result<Vec<i128>> {
let (&form, rest) = payload
.split_first()
.ok_or_else(|| EncodingError::CorruptData {
detail: "empty decimal block".to_string(),
})?;
match form {
FORM_NARROW => Self::decode_narrow(rest),
FORM_WIDE => Self::decode_wide(rest),
other => Err(EncodingError::CorruptData {
detail: format!("unknown decimal form: {other}"),
}),
}
}
fn decode_narrow(rest: &[u8]) -> Result<Vec<i128>> {
let (&tag, inner) = rest
.split_first()
.ok_or_else(|| EncodingError::CorruptData {
detail: "narrow decimal block has no inner encoding tag".to_string(),
})?;
let encoding = EncodingType::from_tag(tag)?;
if encoding == EncodingType::DecimalI128 {
return Err(EncodingError::CorruptData {
detail: "narrow decimal block nests a decimal block".to_string(),
});
}
let block = EncodedBlock {
encoding,
payload: inner.to_vec(),
};
match ColumnDecoder::decode(&block)? {
DecodedColumn::I64(values) => Ok(values.into_iter().map(i128::from).collect()),
other => Err(EncodingError::CorruptData {
detail: format!(
"narrow decimal block decoded to {} values, expected i64",
match other {
DecodedColumn::F64(_) => "f64",
DecodedColumn::U64(_) => "u64",
DecodedColumn::Bool(_) => "bool",
DecodedColumn::String(_) => "string",
_ => "nullable or wide",
}
),
}),
}
}
fn decode_wide(rest: &[u8]) -> Result<Vec<i128>> {
if rest.len() < 4 + 16 {
return Err(EncodingError::CorruptData {
detail: "wide decimal block truncated".to_string(),
});
}
let count = checked_decode_count(
u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize,
"decimal",
)?;
if count == 0 {
return Err(EncodingError::CorruptData {
detail: "wide decimal block declares zero values".to_string(),
});
}
let mut first_bytes = [0u8; 16];
first_bytes.copy_from_slice(&rest[4..20]);
let mut current = i128::from_le_bytes(first_bytes);
let mut values = Vec::with_capacity(count);
values.push(current);
let mut cursor = 20;
for _ in 1..count {
let (zz, used) = varint_decode_u128(&rest[cursor..])?;
cursor += used;
current = current.wrapping_add(zigzag_decode_i128(zz));
values.push(current);
}
Ok(values)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(values: &[i128]) {
let encoded = DecimalEncoder::encode(values).unwrap();
let decoded = DecimalDecoder::decode(&encoded).unwrap();
assert_eq!(decoded, values, "round trip");
}
#[test]
fn narrow_round_trip() {
round_trip(&[23_145]);
round_trip(&[0, 1, 2, 3, 4, 5]);
round_trip(&[-5, -4, -3, 0, 3, 4, 5]);
round_trip(&[i64::MAX as i128, i64::MIN as i128]);
}
#[test]
fn wide_round_trip() {
round_trip(&[i128::from(i64::MAX) + 1]);
round_trip(&[
99_999_999_999_999_999_999_999_999_999_999_999_999,
-99_999_999_999_999_999_999_999_999_999_999_999_999,
0,
]);
}
#[test]
fn form_is_narrow_when_every_mantissa_fits_i64() {
let encoded = DecimalEncoder::encode(&[1, 2, 3]).unwrap();
assert_eq!(encoded[0], FORM_NARROW);
let encoded = DecimalEncoder::encode(&[i128::from(i64::MAX) + 1]).unwrap();
assert_eq!(encoded[0], FORM_WIDE);
}
#[test]
fn a_monotone_register_compresses() {
let values: Vec<i128> = (0..4096).map(|i| 10_000_000_000 + i * 2_500_000).collect();
let encoded = DecimalEncoder::encode(&values).unwrap();
let raw = values.len() * 16;
assert!(
encoded.len() * 8 < raw,
"expected >8x on a monotone register, got {} vs {raw}",
encoded.len()
);
assert_eq!(DecimalDecoder::decode(&encoded).unwrap(), values);
}
#[test]
fn a_constant_register_compresses_hard() {
let values = vec![42_0000i128; 4096];
let encoded = DecimalEncoder::encode(&values).unwrap();
assert!(
encoded.len() < 128,
"constant column: {} bytes",
encoded.len()
);
assert_eq!(DecimalDecoder::decode(&encoded).unwrap(), values);
}
#[test]
fn empty_input_is_an_error() {
assert!(matches!(
DecimalEncoder::encode(&[]),
Err(EncodingError::EmptyInput { .. })
));
}
#[test]
fn corrupt_form_byte_is_an_error() {
assert!(DecimalDecoder::decode(&[]).is_err());
assert!(DecimalDecoder::decode(&[7]).is_err());
}
#[test]
fn a_nested_decimal_tag_does_not_recurse() {
let nested = [FORM_NARROW, EncodingType::DecimalI128.tag(), 0, 0];
assert!(DecimalDecoder::decode(&nested).is_err());
}
#[test]
fn truncated_wide_block_is_an_error() {
let encoded = DecimalEncoder::encode(&[i128::from(i64::MAX) + 1, 5, 9]).unwrap();
for cut in 1..encoded.len() {
let _ = DecimalDecoder::decode(&encoded[..cut]);
}
}
#[test]
fn a_corrupt_count_cannot_allocate_the_machine_away() {
let mut encoded = DecimalEncoder::encode(&[i128::from(i64::MAX) + 1]).unwrap();
encoded[1..5].copy_from_slice(&u32::MAX.to_le_bytes());
assert!(DecimalDecoder::decode(&encoded).is_err());
}
#[test]
fn varint_round_trip_at_the_boundaries() {
for value in [0u128, 1, 127, 128, u128::from(u64::MAX), u128::MAX] {
let mut buf = Vec::new();
varint_encode_u128(value, &mut buf);
let (decoded, used) = varint_decode_u128(&buf).unwrap();
assert_eq!(decoded, value);
assert_eq!(used, buf.len());
}
}
#[test]
fn zigzag_round_trip_at_the_boundaries() {
for value in [0i128, 1, -1, i128::MAX, i128::MIN, i128::from(i64::MIN)] {
assert_eq!(zigzag_decode_i128(zigzag_encode_i128(value)), value);
}
}
}