use rudb_common::{Error, LogicalType, PhysicalType, Result, Value};
use rudb_plan::SortKey;
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)
}
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(())
}
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 super::{Normal, WIDTH, layout, write};
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 = rudb_common::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(&[rudb_common::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(&[rudb_common::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 = [
rudb_common::LogicalType::Date,
rudb_common::LogicalType::BigInt,
rudb_common::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 rudb_common::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(&[rudb_common::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");
}
}
}