#[cfg(test)]
use rudb_common::Value;
use rudb_common::{Error, LogicalType, PhysicalType, Result};
use rudb_plan::SortKey;
use rudb_vector::{Data, Validity, Vector};
pub(crate) const WIDTH: usize = 24;
pub(crate) type Normal = [u8; WIDTH];
pub(crate) fn layout(types: &[LogicalType]) -> Option<Vec<usize>> {
let mut widths = Vec::with_capacity(types.len());
let mut total = 0;
for ty in types {
let wide = wide(ty)?;
total += wide;
if total > WIDTH {
return None;
}
widths.push(wide);
}
(!widths.is_empty()).then_some(widths)
}
fn wide(ty: &LogicalType) -> Option<usize> {
let ordered = matches!(
ty,
LogicalType::Boolean
| LogicalType::TinyInt
| LogicalType::SmallInt
| LogicalType::Integer
| LogicalType::BigInt
| LogicalType::HugeInt
| LogicalType::UTinyInt
| LogicalType::USmallInt
| LogicalType::UInteger
| LogicalType::UBigInt
| LogicalType::UHugeInt
| LogicalType::Date
| LogicalType::Time
| LogicalType::TimeTz
| LogicalType::Timestamp
| LogicalType::TimestampTz
| LogicalType::Decimal { .. }
);
if !ordered {
return None;
}
let payload = match ty.physical() {
PhysicalType::Bool | PhysicalType::Int8 | PhysicalType::UInt8 => 1,
PhysicalType::Int16 | PhysicalType::UInt16 => 2,
PhysicalType::Int32 | PhysicalType::UInt32 => 4,
PhysicalType::Int64 | PhysicalType::UInt64 => 8,
PhysicalType::Int128 | PhysicalType::UInt128 => 16,
_ => return None,
};
Some(payload + 1)
}
#[cfg(test)]
pub(crate) fn write(
into: &mut Normal,
at: usize,
wide: usize,
value: &Value,
key: SortKey,
) -> Result<()> {
let Some(slot) = into.get_mut(at..at + wide) else {
return Err(Error::internal("a normalized sort key wider than the buffer holding it"));
};
let (tag, payload) = slot.split_at_mut(1);
if value.is_null() {
tag[0] = u8::from(!key.nulls_first);
return Ok(());
}
tag[0] = u8::from(key.nulls_first);
let ordered = ordered(value, payload.len())?.to_be_bytes();
let Some(bytes) = ordered.get(16 - payload.len()..) else {
return Err(Error::internal("a normalized sort key narrower than the value in it"));
};
payload.copy_from_slice(bytes);
if key.descending {
for byte in payload {
*byte = !*byte;
}
}
Ok(())
}
pub(crate) fn write_column<'a>(
into: impl ExactSizeIterator<Item = &'a mut Normal>,
at: usize,
wide: usize,
column: &Vector,
key: SortKey,
) -> Result<()> {
if into.len() != column.len() {
return Err(Error::internal("a sort key column of a different length than its rows"));
}
let flat = column.flatten()?;
let Some(data) = flat.data() else {
return Err(Error::internal("a flattened sort key with no run of data"));
};
let place = Place { at, wide, key, validity: flat.validity() };
match data {
Data::Bool(values) => place.put(into, values.as_slice(), u128::from),
Data::Int8(values) => {
place.put(into, values.as_slice(), |value| place.signed(value.into()))
}
Data::Int16(values) => {
place.put(into, values.as_slice(), |value| place.signed(value.into()))
}
Data::Int32(values) => {
place.put(into, values.as_slice(), |value| place.signed(value.into()))
}
Data::Int64(values) => {
place.put(into, values.as_slice(), |value| place.signed(value.into()))
}
Data::Int128(values) => place.put(into, values.as_slice(), |value| place.signed(value)),
Data::UInt8(values) => place.put(into, values.as_slice(), u128::from),
Data::UInt16(values) => place.put(into, values.as_slice(), u128::from),
Data::UInt32(values) => place.put(into, values.as_slice(), u128::from),
Data::UInt64(values) => place.put(into, values.as_slice(), u128::from),
Data::UInt128(values) => place.put(into, values.as_slice(), |value| value),
_ => Err(Error::internal(format!(
"a {} column reached the normalized sort key path",
column.logical_type()
))),
}
}
struct Place<'v> {
at: usize,
wide: usize,
key: SortKey,
validity: &'v Validity,
}
impl Place<'_> {
fn signed(&self, value: i128) -> u128 {
let sign = (self.wide - 1).checked_mul(8).and_then(|bits| bits.checked_sub(1)).unwrap_or(0);
(value as u128) ^ (1u128 << sign)
}
fn put<'a, T: Copy>(
&self,
into: impl Iterator<Item = &'a mut Normal>,
values: &[T],
raw: impl Fn(T) -> u128,
) -> Result<()> {
let payload = self.wide - 1;
if payload != size_of::<T>() {
return Err(Error::internal("a sort key column wider or narrower than its key"));
}
let all = matches!(self.validity, Validity::AllValid);
let (present, absent) = (u8::from(self.key.nulls_first), u8::from(!self.key.nulls_first));
for (row, (normal, &value)) in into.zip(values).enumerate() {
let Some(slot) = normal.get_mut(self.at..self.at + self.wide) else {
return Err(Error::internal(
"a normalized sort key wider than the buffer holding it",
));
};
let (tag, bytes) = slot.split_at_mut(1);
if !all && !self.validity.is_valid(row) {
tag[0] = absent;
bytes.fill(0);
continue;
}
tag[0] = present;
bytes.copy_from_slice(&raw(value).to_be_bytes()[16 - payload..]);
if self.key.descending {
for byte in bytes {
*byte = !*byte;
}
}
}
Ok(())
}
}
#[cfg(test)]
fn ordered(value: &Value, bytes: usize) -> Result<u128> {
let (raw, signed) = match value {
Value::Boolean(held) => (u128::from(*held), false),
Value::TinyInt(held) => (i128::from(*held) as u128, true),
Value::SmallInt(held) => (i128::from(*held) as u128, true),
Value::Integer(held) | Value::Date(held) => (i128::from(*held) as u128, true),
Value::BigInt(held)
| Value::Time(held)
| Value::TimeTz(held)
| Value::Timestamp(held)
| Value::TimestampTz(held) => (i128::from(*held) as u128, true),
Value::HugeInt(held) => (*held as u128, true),
Value::UTinyInt(held) => (u128::from(*held), false),
Value::USmallInt(held) => (u128::from(*held), false),
Value::UInteger(held) => (u128::from(*held), false),
Value::UBigInt(held) => (u128::from(*held), false),
Value::UHugeInt(held) => (*held, false),
Value::Decimal { unscaled, .. } => (*unscaled as u128, true),
other => {
return Err(Error::internal(format!(
"a {} reached the normalized sort key path",
other.logical_type()
)));
}
};
let sign = bytes.checked_mul(8).and_then(|bits| bits.checked_sub(1)).unwrap_or(0);
Ok(if signed { raw ^ (1u128 << sign) } else { raw })
}
#[cfg(test)]
mod tests {
use rudb_common::Value;
use rudb_plan::SortKey;
use rudb_common::LogicalType;
use rudb_vector::Vector;
use super::{Normal, WIDTH, layout, write, write_column};
fn key(descending: bool, nulls_first: bool) -> SortKey {
SortKey { expr: 0, descending, nulls_first }
}
fn one(value: &Value, wide: usize, key: SortKey) -> Normal {
let mut into: Normal = [0; WIDTH];
write(&mut into, 0, wide, value, key).expect("a type the layout accepted");
into
}
#[test]
fn a_signed_column_encodes_into_the_order_it_compares_in() {
let ty = LogicalType::Integer;
let widths = layout(&[ty]).expect("an integer normalizes");
let ascending = key(false, false);
let mut held: Vec<i32> = vec![i32::MIN, -70000, -1, 0, 1, 255, 256, 70000, i32::MAX];
held.sort_unstable();
let encoded: Vec<Normal> =
held.iter().map(|&v| one(&Value::Integer(v), widths[0], ascending)).collect();
for pair in encoded.windows(2) {
assert!(pair[0] < pair[1], "the bytes should rise with the values");
}
}
#[test]
fn a_descending_key_reverses_the_values_and_not_the_nulls() {
let widths = layout(&[LogicalType::BigInt]).expect("a bigint normalizes");
let falling = key(true, false);
let low = one(&Value::BigInt(1), widths[0], falling);
let high = one(&Value::BigInt(9), widths[0], falling);
let none = one(&Value::Null, widths[0], falling);
assert!(high < low, "descending puts the larger value first");
assert!(low < none, "nulls last puts a null after every value, direction or not");
let rising_first = key(false, true);
let none = one(&Value::Null, widths[0], rising_first);
let low = one(&Value::BigInt(1), widths[0], rising_first);
assert!(none < low, "nulls first puts a null before every value");
}
#[test]
fn two_nulls_encode_the_same_bytes() {
let widths = layout(&[LogicalType::Date]).expect("a date normalizes");
for falling in [false, true] {
for first in [false, true] {
let at = key(falling, first);
assert_eq!(one(&Value::Null, widths[0], at), one(&Value::Null, widths[0], at));
}
}
}
#[test]
fn the_first_key_decides_before_the_second_is_looked_at() {
let types = [LogicalType::Date, LogicalType::BigInt, LogicalType::Integer];
let widths = layout(&types).expect("the clustered layout normalizes");
assert_eq!(widths, vec![5, 9, 5], "a tag and the payload, per key");
let rising = key(false, false);
let pack = |day: i32, order: i64, line: i32| {
let mut into: Normal = [0; WIDTH];
let mut at = 0;
for (wide, value) in
widths.iter().zip([Value::Date(day), Value::BigInt(order), Value::Integer(line)])
{
write(&mut into, at, *wide, &value, rising).expect("encodes");
at += wide;
}
into
};
assert!(pack(1, 9, 9) < pack(2, 0, 0), "the date decides first");
assert!(pack(1, 1, 9) < pack(1, 2, 0), "then the order key");
assert!(pack(1, 1, 1) < pack(1, 1, 2), "then the line number");
assert_eq!(pack(1, 1, 1), pack(1, 1, 1), "and equal rows encode equal");
}
#[test]
fn a_key_list_this_cannot_hold_takes_the_other_path() {
use LogicalType as T;
assert!(layout(&[T::Varchar]).is_none(), "a string has no fixed width");
assert!(layout(&[T::Double]).is_none(), "a double does not order like its bytes");
assert!(layout(&[T::Interval]).is_none(), "an interval orders over its fields folded");
assert!(layout(&[T::BigInt, T::Varchar]).is_none(), "one bad key refuses the list");
assert!(layout(&[T::HugeInt]).is_some(), "seventeen bytes fits");
assert!(layout(&[T::HugeInt, T::HugeInt]).is_none(), "thirty four does not");
assert!(layout(&[]).is_none(), "and a sort with no keys is not a sort");
assert_eq!(layout(&[T::Decimal { width: 9, scale: 2 }]), Some(vec![5]));
assert_eq!(layout(&[T::Decimal { width: 38, scale: 2 }]), Some(vec![17]));
}
#[test]
fn an_unsigned_column_encodes_without_the_bias() {
let widths = layout(&[LogicalType::UBigInt]).expect("normalizes");
let rising = key(false, false);
let held = [0u64, 1, u64::from(u32::MAX), u64::MAX];
let encoded: Vec<Normal> =
held.iter().map(|&v| one(&Value::UBigInt(v), widths[0], rising)).collect();
for pair in encoded.windows(2) {
assert!(pair[0] < pair[1], "the bytes should rise with the values");
}
}
#[test]
fn a_column_writes_the_bytes_its_values_write_one_at_a_time() {
let columns = [
(LogicalType::Boolean, vec![Value::Boolean(true), Value::Null, Value::Boolean(false)]),
(LogicalType::SmallInt, vec![Value::SmallInt(-2), Value::SmallInt(7), Value::Null]),
(LogicalType::Date, vec![Value::Date(-1), Value::Null, Value::Date(19000)]),
(LogicalType::BigInt, vec![Value::Null, Value::BigInt(i64::MIN), Value::BigInt(3)]),
(
LogicalType::UInteger,
vec![Value::UInteger(0), Value::UInteger(u32::MAX), Value::Null],
),
(
LogicalType::Decimal { width: 38, scale: 2 },
vec![
Value::Decimal { unscaled: -5, width: 38, scale: 2 },
Value::Null,
Value::Decimal { unscaled: 12, width: 38, scale: 2 },
],
),
];
for (ty, values) in columns {
let widths = layout(&[ty.clone(), LogicalType::Integer]).expect("normalizes");
let column = Vector::from_values(ty.clone(), &values).expect("a column of them");
let after = Vector::from_values(LogicalType::Integer, &vec![Value::Integer(-9); 3])
.expect("a second key");
for (descending, nulls_first) in
[(false, false), (false, true), (true, false), (true, true)]
{
let first = key(descending, nulls_first);
let second = key(false, false);
let mut together: Vec<Normal> = vec![[0; WIDTH]; values.len()];
write_column(together.iter_mut(), 0, widths[0], &column, first).expect("writes");
write_column(together.iter_mut(), widths[0], widths[1], &after, second)
.expect("writes");
for (row, value) in values.iter().enumerate() {
let mut alone: Normal = [0; WIDTH];
write(&mut alone, 0, widths[0], value, first).expect("writes");
write(&mut alone, widths[0], widths[1], &Value::Integer(-9), second)
.expect("writes");
assert_eq!(together[row], alone, "{ty} row {row} desc {descending}");
}
}
}
}
}