use super::super::bits::{bit_width, pack_mapped, unpack_one};
use super::iter::ColumnIter;
use crate::symbol::SymbolId;
use std::mem::size_of;
pub trait ColumnValue: Copy + PartialEq {
const IS_INTEGER: bool;
fn to_bits(self) -> u64;
fn from_bits(bits: u64) -> Option<Self>;
}
macro_rules! unsigned_column_value {
($($type:ty),*) => {
$(impl ColumnValue for $type {
const IS_INTEGER: bool = true;
fn to_bits(self) -> u64 {
u64::from(self)
}
fn from_bits(bits: u64) -> Option<Self> {
Self::try_from(bits).ok()
}
})*
};
}
macro_rules! signed_column_value {
($($type:ty),*) => {
$(impl ColumnValue for $type {
const IS_INTEGER: bool = true;
fn to_bits(self) -> u64 {
i64::from(self).cast_unsigned()
}
fn from_bits(bits: u64) -> Option<Self> {
Self::try_from(bits.cast_signed()).ok()
}
})*
};
}
unsigned_column_value!(u8, u16, u32);
signed_column_value!(i8, i16, i32);
impl ColumnValue for u64 {
const IS_INTEGER: bool = true;
fn to_bits(self) -> u64 {
self
}
fn from_bits(bits: u64) -> Option<Self> {
Some(bits)
}
}
impl ColumnValue for i64 {
const IS_INTEGER: bool = true;
fn to_bits(self) -> u64 {
self.cast_unsigned()
}
fn from_bits(bits: u64) -> Option<Self> {
Some(bits.cast_signed())
}
}
impl ColumnValue for f32 {
const IS_INTEGER: bool = false;
fn to_bits(self) -> u64 {
u64::from(self.to_bits())
}
fn from_bits(bits: u64) -> Option<Self> {
u32::try_from(bits).ok().map(Self::from_bits)
}
}
impl ColumnValue for SymbolId {
const IS_INTEGER: bool = true;
fn to_bits(self) -> u64 {
u64::from(self.get())
}
fn from_bits(bits: u64) -> Option<Self> {
u32::try_from(bits).ok().map(Self::from_raw)
}
}
#[derive(Clone, PartialEq, Debug)]
pub enum EncodedColumn<T> {
Plain(Vec<T>),
Constant {
value: T,
len: u32,
},
RunLength {
values: Vec<T>,
run_ends: Vec<u32>,
},
BitPacked {
data: Vec<u8>,
width: u8,
len: u32,
},
Delta {
first: T,
deltas: Vec<i32>,
checkpoints: Vec<i64>,
},
}
impl<T: ColumnValue> EncodedColumn<T> {
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Plain(values) => values.len(),
Self::Constant { len, .. } | Self::BitPacked { len, .. } => *len as usize,
Self::RunLength { run_ends, .. } => match run_ends.last() {
Some(end) => *end as usize,
None => 0,
},
Self::Delta { deltas, .. } => deltas.len() + 1,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub const fn is_constant(&self) -> bool {
matches!(self, Self::Constant { .. })
}
#[must_use]
pub fn as_slice(&self) -> Option<&[T]> {
match self {
Self::Plain(values) => Some(values),
_ => None,
}
}
#[must_use]
pub fn get(&self, position: u32) -> Option<T> {
if position as usize >= self.len() {
return None;
}
match self {
Self::Plain(values) => copied_at(values, position),
Self::Constant { value, .. } => Some(*value),
Self::RunLength { values, run_ends } => {
let run = run_ends.partition_point(|end| *end <= position);
values.get(run).copied()
}
Self::BitPacked { data, width, .. } => {
unpack_one(data, *width, position).and_then(T::from_bits)
}
Self::Delta {
first,
deltas,
checkpoints,
} => delta_at(*first, deltas, checkpoints, position),
}
}
pub fn iter(&self) -> ColumnIter<'_, T> {
ColumnIter::new(self)
}
#[must_use]
pub fn encode(values: &[T]) -> Self {
let Some(first) = values.first() else {
return Self::Plain(Vec::new());
};
if values.iter().skip(1).all(|value| value == first)
&& let Ok(len) = u32::try_from(values.len())
{
return Self::Constant { value: *first, len };
}
if let Some(column) = Self::try_run_length(values) {
return column;
}
if let Some(column) = Self::try_bit_packed(values) {
return column;
}
if let Some(column) = Self::try_delta(values) {
return column;
}
Self::Plain(copy_values(values))
}
#[must_use]
pub fn plain(values: &[T]) -> Self {
Self::Plain(copy_values(values))
}
fn try_run_length(values: &[T]) -> Option<Self> {
let runs = run_count(values);
if runs > values.len() / 4 {
return None;
}
let mut heads = Vec::with_capacity(runs);
let mut run_ends = Vec::with_capacity(runs);
for (position, value) in values.iter().enumerate() {
let end = u32::try_from(position.checked_add(1)?).ok()?;
if heads.last() == Some(value) {
if let Some(last_end) = run_ends.last_mut() {
*last_end = end;
}
continue;
}
heads.push(*value);
run_ends.push(end);
}
Some(Self::RunLength {
values: heads,
run_ends,
})
}
fn try_bit_packed(values: &[T]) -> Option<Self> {
if !T::IS_INTEGER {
return None;
}
let max = values.iter().map(|value| value.to_bits()).max()?;
let width = bit_width(max);
if usize::from(width) >= size_of::<T>() * 8 {
return None;
}
Some(Self::BitPacked {
data: pack_mapped(values, width, |value| value.to_bits())?,
width,
len: u32::try_from(values.len()).ok()?,
})
}
fn try_delta(values: &[T]) -> Option<Self> {
if !T::IS_INTEGER {
return None;
}
let first = *values.first()?;
let delta_count = if values.is_empty() {
0
} else {
values.len() - 1
};
let mut deltas = Vec::with_capacity(delta_count);
for pair in values.windows(2) {
let [earlier, later] = pair else {
return None;
};
let step = later
.to_bits()
.cast_signed()
.checked_sub(earlier.to_bits().cast_signed())?;
deltas.push(i32::try_from(step).ok()?);
}
let mut checkpoints = Vec::with_capacity(deltas.len() / DELTA_CHECKPOINT_STRIDE);
let mut running = first.to_bits().cast_signed();
for (index, delta) in deltas.iter().enumerate() {
running = running.checked_add(i64::from(*delta))?;
if (index + 1).is_multiple_of(DELTA_CHECKPOINT_STRIDE) {
checkpoints.push(running);
}
}
Some(Self::Delta {
first,
deltas,
checkpoints,
})
}
}
fn run_count<T: PartialEq>(values: &[T]) -> usize {
if values.is_empty() {
return 0;
}
1 + values
.windows(2)
.filter(|pair| pair.first() != pair.get(1))
.count()
}
fn copy_values<T: Copy>(values: &[T]) -> Vec<T> {
values.to_vec()
}
fn copied_at<T: Copy>(values: &[T], position: u32) -> Option<T> {
usize::try_from(position)
.ok()
.and_then(|index| values.get(index))
.copied()
}
const DELTA_CHECKPOINT_STRIDE: usize = 64;
fn delta_at<T: ColumnValue>(
first: T,
deltas: &[i32],
checkpoints: &[i64],
position: u32,
) -> Option<T> {
let end = usize::try_from(position).ok()?;
if end > deltas.len() {
return None;
}
let block = end / DELTA_CHECKPOINT_STRIDE;
let (start, base) = match block.checked_sub(1).and_then(|slot| checkpoints.get(slot)) {
Some(checkpoint) => (block * DELTA_CHECKPOINT_STRIDE, *checkpoint),
None => (0, first.to_bits().cast_signed()),
};
let value = deltas
.get(start..end)?
.iter()
.fold(base, |value, delta| value + i64::from(*delta));
T::from_bits(value.cast_unsigned())
}
#[cfg(test)]
#[path = "model_tests.rs"]
mod tests;