#![allow(clippy::unwrap_used, clippy::expect_used)]
use chronix_encoding::*;
const CEILING: usize = 1 << 24;
const ABSURD: u32 = u32::MAX;
#[test]
fn alp_rejects_an_absurd_value_count() {
let mut block = Vec::new();
block.extend_from_slice(&ABSURD.to_le_bytes()); block.push(0); block.push(0); block.push(0); block.extend_from_slice(&0i64.to_le_bytes()); block.extend_from_slice(&0u32.to_le_bytes());
assert_eq!(block.len(), 19, "the whole input is 19 bytes");
let err = AlpDecoder::decode(&block)
.expect_err("a 19-byte block claiming u32::MAX values must be refused");
let msg = err.to_string();
assert!(
msg.contains("ceiling") || msg.contains("corrupt"),
"the error should name the ceiling, got: {msg}"
);
}
#[test]
fn alp_rejects_an_absurd_exception_count() {
let mut block = Vec::new();
block.extend_from_slice(&1u32.to_le_bytes()); block.push(0);
block.push(0);
block.push(0); block.extend_from_slice(&0i64.to_le_bytes());
block.extend_from_slice(&ABSURD.to_le_bytes());
assert!(
AlpDecoder::decode(&block).is_err(),
"an absurd exception count must be refused"
);
}
#[test]
fn for_encoding_rejects_an_absurd_value_count() {
let mut block = Vec::new();
block.extend_from_slice(&ABSURD.to_le_bytes());
block.extend_from_slice(&7i64.to_le_bytes());
block.push(0);
assert!(
ForDecoder::decode_i64(&block).is_err(),
"FOR i64 must refuse an absurd count"
);
assert!(
ForDecoder::decode_u64(&block).is_err(),
"FOR u64 must refuse an absurd count"
);
}
#[test]
fn plain_rejects_an_absurd_value_count() {
let block = ABSURD.to_le_bytes().to_vec();
assert!(PlainDecoder::decode_f64(&block).is_err());
assert!(PlainDecoder::decode_i64(&block).is_err());
assert!(PlainDecoder::decode_u64(&block).is_err());
assert!(PlainDecoder::decode_string(&block).is_err());
assert!(PlainDecoder::decode_bool(&block).is_err());
}
#[test]
fn bitmap_rejects_an_absurd_value_count() {
let block = ABSURD.to_le_bytes().to_vec();
assert!(BitmapDecoder::decode(&block).is_err());
}
#[test]
fn decimal_rejects_an_absurd_value_count() {
use chronix_encoding::DecimalDecoder;
let mut wide = vec![1u8];
wide.extend_from_slice(&ABSURD.to_le_bytes());
wide.extend_from_slice(&0i128.to_le_bytes());
assert!(
DecimalDecoder::decode(&wide).is_err(),
"an absurd value count must be refused before the allocation"
);
let nested = [0u8, EncodingType::DecimalI128.tag(), 0, 0];
assert!(
DecimalDecoder::decode(&nested).is_err(),
"a nested decimal block must be refused, not decoded"
);
let block = EncodedBlock {
encoding: EncodingType::DecimalI128,
payload: wide,
};
assert!(ColumnDecoder::decode(&block).is_err());
}
#[test]
fn dictionary_rejects_absurd_counts() {
let mut absurd_values = Vec::new();
absurd_values.extend_from_slice(&ABSURD.to_le_bytes());
absurd_values.extend_from_slice(&0u32.to_le_bytes());
assert!(
DictionaryDecoder::decode(&absurd_values).is_err(),
"an absurd value count must be refused"
);
let mut absurd_dict = Vec::new();
absurd_dict.extend_from_slice(&1u32.to_le_bytes());
absurd_dict.extend_from_slice(&ABSURD.to_le_bytes());
assert!(
DictionaryDecoder::decode(&absurd_dict).is_err(),
"an absurd dictionary size must be refused"
);
}
#[test]
fn the_ceiling_is_far_above_any_legitimate_block() {
assert_eq!(CEILING, 16_777_216);
assert_eq!(
CEILING / 65_536,
256,
"the ceiling must leave generous headroom above the default row group"
);
}
#[test]
fn realistic_blocks_still_round_trip() {
let values: Vec<f64> = (0..65_536).map(|i| f64::from(i) * 0.01).collect();
let encoded = AlpEncoder::encode(&values).unwrap();
let decoded = AlpDecoder::decode(&encoded).unwrap();
assert_eq!(decoded.len(), values.len());
assert_eq!(decoded, values);
}
#[test]
fn pco_rejects_an_absurd_value_count_on_every_entry_point() {
let mut block = Vec::new();
block.extend_from_slice(&ABSURD.to_le_bytes());
block.extend_from_slice(&[0u8; 16]);
for enc in [
EncodingType::Pco,
EncodingType::PcoI64,
EncodingType::PcoU64,
] {
let err = ColumnDecoder::decode(&EncodedBlock {
encoding: enc,
payload: block.clone(),
})
.expect_err("a pco block claiming u32::MAX values must be refused");
assert!(
err.to_string().contains("ceiling"),
"{enc}: the error should name the ceiling, got: {err}"
);
let mut nullable = vec![enc.tag()];
nullable.extend_from_slice(&1u32.to_le_bytes());
nullable.push(0b1);
nullable.extend_from_slice(&block);
let err = ColumnDecoder::decode(&EncodedBlock {
encoding: EncodingType::Nullable,
payload: nullable,
})
.expect_err("a nullable pco block claiming u32::MAX values must be refused");
assert!(
err.to_string().contains("ceiling"),
"nullable {enc}: the error should name the ceiling, got: {err}"
);
}
let values: Vec<i64> = (0..100).collect();
let mut enc = PcoEncoder::encode_i64(&values).unwrap();
enc[..4].copy_from_slice(&(CEILING as u32).to_le_bytes());
assert!(
PcoDecoder::decode_i64(&enc).is_err(),
"a count inside the ceiling that the body does not fill is a count mismatch"
);
}