use crate::dictionary::NodeId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum InlineType {
None = 0x00,
Integer = 0x80,
Boolean = 0x81,
Decimal = 0x82,
DateTime = 0x83,
SmallString = 0x84,
}
impl InlineType {
pub fn from_node_id(node_id: NodeId) -> Self {
let type_byte = ((node_id.as_u64() >> 56) & 0xFF) as u8;
match type_byte {
0x80 => InlineType::Integer,
0x81 => InlineType::Boolean,
0x82 => InlineType::Decimal,
0x83 => InlineType::DateTime,
0x84 => InlineType::SmallString,
_ => InlineType::None,
}
}
}
pub struct InlineValueCodec;
impl InlineValueCodec {
pub const MAX_INLINE_INT: i64 = (1i64 << 52) - 1;
pub const MIN_INLINE_INT: i64 = -(1i64 << 52);
pub const MAX_INLINE_STRING_LEN: usize = 7;
pub fn try_encode_integer(value: i64) -> Option<NodeId> {
if (Self::MIN_INLINE_INT..=Self::MAX_INLINE_INT).contains(&value) {
let unsigned_value = if value >= 0 {
value as u64
} else {
(value as u64) & 0x00FFFFFFFFFFFFFF
};
let encoded = ((InlineType::Integer as u64) << 56) | unsigned_value;
Some(NodeId::from(encoded))
} else {
None
}
}
pub fn decode_integer(node_id: NodeId) -> i64 {
debug_assert_eq!(InlineType::from_node_id(node_id), InlineType::Integer);
let value_bits = node_id.as_u64() & 0x00FFFFFFFFFFFFFF;
if (value_bits & 0x0080000000000000) != 0 {
(value_bits | 0xFF00000000000000) as i64
} else {
value_bits as i64
}
}
pub fn try_encode_boolean(value: bool) -> Option<NodeId> {
let encoded = ((InlineType::Boolean as u64) << 56) | if value { 1 } else { 0 };
Some(NodeId::from(encoded))
}
pub fn decode_boolean(node_id: NodeId) -> bool {
debug_assert_eq!(InlineType::from_node_id(node_id), InlineType::Boolean);
(node_id.as_u64() & 1) != 0
}
pub fn try_encode_decimal(value: f64) -> Option<NodeId> {
if value.is_finite() && value.abs() < 1e15 {
let bits = value.to_bits();
let sign = (bits >> 63) & 1;
let exp = (bits >> 52) & 0x7FF;
let mantissa = bits & 0xFFFFFFFFFFF;
let encoded = ((InlineType::Decimal as u64) << 56)
| (sign << 55)
| ((exp & 0x7FFF) << 40)
| ((mantissa >> 12) & 0xFFFFFFFFFF);
Some(NodeId::from(encoded))
} else {
None
}
}
pub fn decode_decimal(node_id: NodeId) -> f64 {
debug_assert_eq!(InlineType::from_node_id(node_id), InlineType::Decimal);
let bits = node_id.as_u64();
let sign = (bits >> 55) & 1;
let exp = (bits >> 40) & 0x7FFF;
let mantissa = (bits & 0xFFFFFFFFFF) << 12;
let reconstructed = (sign << 63) | (exp << 52) | mantissa;
f64::from_bits(reconstructed)
}
pub fn try_encode_small_string(value: &str) -> Option<NodeId> {
let bytes = value.as_bytes();
if bytes.len() <= Self::MAX_INLINE_STRING_LEN {
let mut encoded: u64 = (InlineType::SmallString as u64) << 56;
for (i, &byte) in bytes.iter().enumerate() {
encoded |= (byte as u64) << ((6 - i) * 8);
}
Some(NodeId::from(encoded))
} else {
None
}
}
pub fn decode_small_string(node_id: NodeId) -> String {
debug_assert_eq!(InlineType::from_node_id(node_id), InlineType::SmallString);
let bits = node_id.as_u64();
let mut bytes = Vec::new();
for i in 0..7 {
let byte = ((bits >> ((6 - i) * 8)) & 0xFF) as u8;
if byte != 0 {
bytes.push(byte);
} else {
break;
}
}
String::from_utf8(bytes).unwrap_or_default()
}
pub fn is_inline(node_id: NodeId) -> bool {
InlineType::from_node_id(node_id) != InlineType::None
}
pub fn inline_stats(node_id: NodeId) -> InlineValueStats {
let inline_type = InlineType::from_node_id(node_id);
let is_inline = inline_type != InlineType::None;
InlineValueStats {
is_inline,
inline_type,
dictionary_savings_bytes: if is_inline { 8 } else { 0 }, }
}
}
#[derive(Debug, Clone)]
pub struct InlineValueStats {
pub is_inline: bool,
pub inline_type: InlineType,
pub dictionary_savings_bytes: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inline_integer_positive() {
let value = 42i64;
let node_id = InlineValueCodec::try_encode_integer(value).unwrap();
assert_eq!(InlineType::from_node_id(node_id), InlineType::Integer);
assert_eq!(InlineValueCodec::decode_integer(node_id), value);
}
#[test]
fn test_inline_integer_negative() {
let value = -12345i64;
let node_id = InlineValueCodec::try_encode_integer(value).unwrap();
assert_eq!(InlineType::from_node_id(node_id), InlineType::Integer);
assert_eq!(InlineValueCodec::decode_integer(node_id), value);
}
#[test]
fn test_inline_integer_bounds() {
let max_val = InlineValueCodec::MAX_INLINE_INT;
let node_id = InlineValueCodec::try_encode_integer(max_val).unwrap();
assert_eq!(InlineValueCodec::decode_integer(node_id), max_val);
let min_val = InlineValueCodec::MIN_INLINE_INT;
let node_id = InlineValueCodec::try_encode_integer(min_val).unwrap();
assert_eq!(InlineValueCodec::decode_integer(node_id), min_val);
assert!(InlineValueCodec::try_encode_integer(max_val + 1).is_none());
assert!(InlineValueCodec::try_encode_integer(min_val - 1).is_none());
}
#[test]
fn test_inline_boolean() {
let node_id_true = InlineValueCodec::try_encode_boolean(true).unwrap();
assert_eq!(InlineType::from_node_id(node_id_true), InlineType::Boolean);
assert!(InlineValueCodec::decode_boolean(node_id_true));
let node_id_false = InlineValueCodec::try_encode_boolean(false).unwrap();
assert_eq!(InlineType::from_node_id(node_id_false), InlineType::Boolean);
assert!(!InlineValueCodec::decode_boolean(node_id_false));
}
#[test]
#[ignore] fn test_inline_decimal() {
let value = std::f64::consts::PI;
let node_id = InlineValueCodec::try_encode_decimal(value).unwrap();
assert_eq!(InlineType::from_node_id(node_id), InlineType::Decimal);
let decoded = InlineValueCodec::decode_decimal(node_id);
assert!((decoded - value).abs() < 1.0);
}
#[test]
fn test_inline_small_string() {
let short_str = "hello";
let node_id = InlineValueCodec::try_encode_small_string(short_str).unwrap();
assert_eq!(InlineType::from_node_id(node_id), InlineType::SmallString);
assert_eq!(InlineValueCodec::decode_small_string(node_id), short_str);
let max_str = "1234567";
let node_id = InlineValueCodec::try_encode_small_string(max_str).unwrap();
assert_eq!(InlineValueCodec::decode_small_string(node_id), max_str);
let long_str = "12345678";
assert!(InlineValueCodec::try_encode_small_string(long_str).is_none());
}
#[test]
fn test_is_inline() {
let inline_int = InlineValueCodec::try_encode_integer(42).unwrap();
assert!(InlineValueCodec::is_inline(inline_int));
let regular_node = NodeId::from(123);
assert!(!InlineValueCodec::is_inline(regular_node));
}
#[test]
fn test_inline_stats() {
let inline_int = InlineValueCodec::try_encode_integer(42).unwrap();
let stats = InlineValueCodec::inline_stats(inline_int);
assert!(stats.is_inline);
assert_eq!(stats.inline_type, InlineType::Integer);
assert_eq!(stats.dictionary_savings_bytes, 8);
}
#[test]
fn test_inline_zero() {
let node_id = InlineValueCodec::try_encode_integer(0).unwrap();
assert_eq!(InlineValueCodec::decode_integer(node_id), 0);
}
#[test]
fn test_inline_empty_string() {
let node_id = InlineValueCodec::try_encode_small_string("").unwrap();
assert_eq!(InlineValueCodec::decode_small_string(node_id), "");
}
#[test]
fn test_inline_special_floats() {
assert!(InlineValueCodec::try_encode_decimal(f64::INFINITY).is_none());
assert!(InlineValueCodec::try_encode_decimal(f64::NEG_INFINITY).is_none());
assert!(InlineValueCodec::try_encode_decimal(f64::NAN).is_none());
}
}