use rudb_common::{Error, Result, Value, interval_micros};
use rudb_vector::{Data, Vector};
use crate::key::{canonical, mix, same, spread};
use crate::rows;
const EMPTY: u32 = u32::MAX;
const LIMIT: usize = EMPTY as usize;
const FIRST: usize = 64;
const NOTHING: u64 = 0x9e37_79b9_7f4a_7c15;
#[derive(Debug)]
pub(crate) struct Table {
buckets: Vec<u32>,
columns: Vec<Column>,
hashes: Vec<u64>,
owned: u64,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Probe {
Found(usize),
Vacant(usize),
}
impl Table {
pub(crate) fn new(types: &[rudb_common::LogicalType]) -> Self {
Self {
buckets: vec![EMPTY; FIRST],
columns: types.iter().map(Column::new).collect(),
hashes: Vec::new(),
owned: 0,
}
}
pub(crate) fn len(&self) -> usize {
self.hashes.len()
}
pub(crate) fn owned(&self) -> u64 {
self.owned
}
pub(crate) fn footprint(&self) -> u64 {
let buckets = self.buckets.capacity() * size_of::<u32>();
let hashes = self.hashes.capacity() * size_of::<u64>();
let keys: usize = self.columns.iter().map(Column::footprint).sum();
u64::try_from(buckets + hashes + keys).unwrap_or(u64::MAX)
}
pub(crate) fn probe(&self, hash: u64, keys: &[Vector], row: usize) -> Probe {
let mask = self.buckets.len() - 1;
let mut at = (hash as usize) & mask;
loop {
let slot = self.buckets[at];
if slot == EMPTY {
return Probe::Vacant(at);
}
let slot = slot as usize;
if self.hashes[slot] == hash && self.holds(slot, keys, row) {
return Probe::Found(slot);
}
at = (at + 1) & mask;
}
}
pub(crate) fn insert(
&mut self,
bucket: usize,
hash: u64,
keys: &[Vector],
row: usize,
) -> Result<usize> {
let slot = self.hashes.len();
if slot >= LIMIT {
return Err(Error::out_of_memory(format!(
"a single group by cannot hold more than {LIMIT} groups"
)));
}
for (at, column) in keys.iter().enumerate() {
let value = column.value_at(row);
if !self.columns[at].stores_payload() {
self.owned += rows::owned(&value);
}
self.columns[at].push(value)?;
}
self.hashes.push(hash);
self.buckets[bucket] = slot as u32;
if self.hashes.len() * 2 >= self.buckets.len() {
self.regrow();
}
Ok(slot)
}
fn holds(&self, slot: usize, keys: &[Vector], row: usize) -> bool {
for (at, column) in keys.iter().enumerate() {
if !self.columns[at].holds(slot, column, row) {
return false;
}
}
true
}
fn regrow(&mut self) {
let mut buckets = vec![EMPTY; self.buckets.len() * 2];
let mask = buckets.len() - 1;
for (slot, &hash) in self.hashes.iter().enumerate() {
let mut at = (hash as usize) & mask;
while buckets[at] != EMPTY {
at = (at + 1) & mask;
}
buckets[at] = slot as u32;
}
self.buckets = buckets;
}
pub(crate) fn column(&self, at: usize, range: std::ops::Range<usize>) -> Vec<Value> {
self.columns[at].values(range)
}
}
#[derive(Debug)]
struct Column {
valid: Vec<bool>,
data: StoredData,
}
#[derive(Debug)]
enum StoredData {
Integer(Vec<i32>),
BigInt(Vec<i64>),
Varchar(StringColumn),
Other(Vec<Stored>),
}
impl Column {
fn new(ty: &rudb_common::LogicalType) -> Self {
let data = match ty {
rudb_common::LogicalType::Integer => StoredData::Integer(Vec::new()),
rudb_common::LogicalType::BigInt => StoredData::BigInt(Vec::new()),
rudb_common::LogicalType::Varchar => StoredData::Varchar(StringColumn::default()),
_ => StoredData::Other(Vec::new()),
};
Self { valid: Vec::new(), data }
}
fn push(&mut self, value: Value) -> Result<()> {
let present = !matches!(value, Value::Null);
match (&mut self.data, value) {
(StoredData::Integer(values), Value::Integer(value)) => values.push(value),
(StoredData::Integer(values), Value::Null) => values.push(0),
(StoredData::BigInt(values), Value::BigInt(value)) => values.push(value),
(StoredData::BigInt(values), Value::Null) => values.push(0),
(StoredData::Varchar(values), Value::Varchar(value)) => values.push(value.as_bytes()),
(StoredData::Varchar(values), Value::Null) => values.push(&[]),
(StoredData::Other(values), value) => values.push(Stored::from(value)),
(_, value) => {
return Err(Error::internal(format!(
"a group key column was given a value of the wrong type: {value:?}"
)));
}
}
self.valid.push(present);
Ok(())
}
fn footprint(&self) -> usize {
let values = match &self.data {
StoredData::Integer(values) => values.capacity() * size_of::<i32>(),
StoredData::BigInt(values) => values.capacity() * size_of::<i64>(),
StoredData::Varchar(values) => values.footprint(),
StoredData::Other(values) => values.capacity() * size_of::<Stored>(),
};
values + self.valid.capacity().div_ceil(8)
}
fn holds(&self, slot: usize, column: &Vector, row: usize) -> bool {
if !self.valid[slot] {
return !column.validity().is_valid(row);
}
match &self.data {
StoredData::Integer(values) => {
matches!(column.value_at(row), Value::Integer(value) if value == values[slot])
}
StoredData::BigInt(values) => {
matches!(column.value_at(row), Value::BigInt(value) if value == values[slot])
}
StoredData::Varchar(values) => column.bytes_at(row).map_or_else(
|| same(&Value::Varchar(values.string(slot)), &column.value_at(row)),
|value| value == values.get(slot),
),
StoredData::Other(values) => same(&values[slot].value(), &column.value_at(row)),
}
}
fn values(&self, range: std::ops::Range<usize>) -> Vec<Value> {
range
.map(|slot| {
if !self.valid[slot] {
return Value::Null;
}
match &self.data {
StoredData::Integer(values) => Value::Integer(values[slot]),
StoredData::BigInt(values) => Value::BigInt(values[slot]),
StoredData::Varchar(values) => Value::Varchar(values.string(slot)),
StoredData::Other(values) => values[slot].value(),
}
})
.collect()
}
fn stores_payload(&self) -> bool {
matches!(self.data, StoredData::Varchar(_))
}
}
#[derive(Debug, Default)]
struct StringColumn {
bytes: Vec<u8>,
ends: Vec<usize>,
}
impl StringColumn {
fn push(&mut self, value: &[u8]) {
self.bytes.extend_from_slice(value);
self.ends.push(self.bytes.len());
}
fn get(&self, slot: usize) -> &[u8] {
let start = slot.checked_sub(1).map_or(0, |before| self.ends[before]);
&self.bytes[start..self.ends[slot]]
}
fn string(&self, slot: usize) -> String {
String::from_utf8(self.get(slot).to_vec()).expect("a VARCHAR group key is valid UTF-8")
}
fn footprint(&self) -> usize {
self.bytes.capacity() + self.ends.capacity() * size_of::<usize>()
}
}
#[derive(Debug, Clone)]
enum Stored {
Null,
Boolean(bool),
TinyInt(i8),
SmallInt(i16),
Integer(i32),
BigInt(i64),
HugeInt(i128),
UTinyInt(u8),
USmallInt(u16),
UInteger(u32),
UBigInt(u64),
UHugeInt(u128),
Float(f32),
Double(f64),
Decimal { unscaled: i128, width: u8, scale: u8 },
Varchar(String),
Blob(Vec<u8>),
Date(i32),
Time(i64),
Timestamp(i64),
Interval { months: i32, days: i32, micros: i64 },
Other(Box<Value>),
}
impl From<Value> for Stored {
fn from(value: Value) -> Self {
match value {
Value::Null => Self::Null,
Value::Boolean(v) => Self::Boolean(v),
Value::TinyInt(v) => Self::TinyInt(v),
Value::SmallInt(v) => Self::SmallInt(v),
Value::Integer(v) => Self::Integer(v),
Value::BigInt(v) => Self::BigInt(v),
Value::HugeInt(v) => Self::HugeInt(v),
Value::UTinyInt(v) => Self::UTinyInt(v),
Value::USmallInt(v) => Self::USmallInt(v),
Value::UInteger(v) => Self::UInteger(v),
Value::UBigInt(v) => Self::UBigInt(v),
Value::UHugeInt(v) => Self::UHugeInt(v),
Value::Float(v) => Self::Float(v),
Value::Double(v) => Self::Double(v),
Value::Decimal { unscaled, width, scale } => Self::Decimal { unscaled, width, scale },
Value::Varchar(v) => Self::Varchar(v),
Value::Blob(v) => Self::Blob(v),
Value::Date(v) => Self::Date(v),
Value::Time(v) => Self::Time(v),
Value::Timestamp(v) => Self::Timestamp(v),
Value::Interval { months, days, micros } => Self::Interval { months, days, micros },
other => Self::Other(Box::new(other)),
}
}
}
impl Stored {
fn value(&self) -> Value {
match self {
Self::Null => Value::Null,
Self::Boolean(v) => Value::Boolean(*v),
Self::TinyInt(v) => Value::TinyInt(*v),
Self::SmallInt(v) => Value::SmallInt(*v),
Self::Integer(v) => Value::Integer(*v),
Self::BigInt(v) => Value::BigInt(*v),
Self::HugeInt(v) => Value::HugeInt(*v),
Self::UTinyInt(v) => Value::UTinyInt(*v),
Self::USmallInt(v) => Value::USmallInt(*v),
Self::UInteger(v) => Value::UInteger(*v),
Self::UBigInt(v) => Value::UBigInt(*v),
Self::UHugeInt(v) => Value::UHugeInt(*v),
Self::Float(v) => Value::Float(*v),
Self::Double(v) => Value::Double(*v),
Self::Decimal { unscaled, width, scale } => {
Value::Decimal { unscaled: *unscaled, width: *width, scale: *scale }
}
Self::Varchar(v) => Value::Varchar(v.clone()),
Self::Blob(v) => Value::Blob(v.clone()),
Self::Date(v) => Value::Date(*v),
Self::Time(v) => Value::Time(*v),
Self::Timestamp(v) => Value::Timestamp(*v),
Self::Interval { months, days, micros } => {
Value::Interval { months: *months, days: *days, micros: *micros }
}
Self::Other(v) => (**v).clone(),
}
}
}
pub(crate) fn hash(keys: &[Vector], rows: usize, hashes: &mut Vec<u64>) {
hashes.clear();
hashes.resize(rows, 0);
for column in keys {
fold(column, rows, hashes);
}
for state in hashes.iter_mut() {
*state = spread(*state);
}
}
fn fold(column: &Vector, rows: usize, hashes: &mut [u64]) {
let validity = column.validity();
macro_rules! run {
($values:expr, $word:expr) => {{
let values = $values.as_slice();
let word = $word;
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let one = match values.get(row) {
Some(value) if validity.is_valid(row) => word(*value),
_ => NOTHING,
};
*state = mix(*state, one);
}
return;
}};
}
if let Some(data) = column.data() {
match data {
Data::Bool(values) => run!(values, |x: bool| u64::from(x)),
Data::Int8(values) => run!(values, |x: i8| i64::from(x) as u64),
Data::Int16(values) => run!(values, |x: i16| i64::from(x) as u64),
Data::Int32(values) => run!(values, |x: i32| i64::from(x) as u64),
Data::Int64(values) => run!(values, |x: i64| x as u64),
Data::UInt8(values) => run!(values, |x: u8| u64::from(x)),
Data::UInt16(values) => run!(values, |x: u16| u64::from(x)),
Data::UInt32(values) => run!(values, |x: u32| u64::from(x)),
Data::UInt64(values) => run!(values, |x: u64| x),
Data::Float32(values) => run!(values, |x: f32| canonical(f64::from(x))),
Data::Float64(values) => run!(values, canonical),
Data::Varlen(strings) => {
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let one = match strings.bytes(row) {
Some(bytes) if validity.is_valid(row) => bytes_word(bytes),
_ => NOTHING,
};
*state = mix(*state, one);
}
return;
}
_ => {}
}
}
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
*state = if column.logical_type() == &rudb_common::LogicalType::Varchar {
match column.bytes_at(row) {
Some(bytes) => mix(*state, bytes_word(bytes)),
None => mix(*state, NOTHING),
}
} else {
fold_value(*state, &column.value_at(row))
};
}
}
fn fold_value(state: u64, value: &Value) -> u64 {
match value {
Value::Null => mix(state, NOTHING),
Value::Boolean(x) => mix(state, u64::from(*x)),
Value::TinyInt(x) => mix(state, i64::from(*x) as u64),
Value::SmallInt(x) => mix(state, i64::from(*x) as u64),
Value::Integer(x) | Value::Date(x) => mix(state, i64::from(*x) as u64),
Value::BigInt(x) | Value::Time(x) | Value::Timestamp(x) => mix(state, *x as u64),
Value::UTinyInt(x) => mix(state, u64::from(*x)),
Value::USmallInt(x) => mix(state, u64::from(*x)),
Value::UInteger(x) => mix(state, u64::from(*x)),
Value::UBigInt(x) => mix(state, *x),
Value::Float(x) => mix(state, canonical(f64::from(*x))),
Value::Double(x) => mix(state, canonical(*x)),
Value::Varchar(x) => mix(state, bytes_word(x.as_bytes())),
Value::Blob(x) => mix(state, bytes_word(x)),
Value::HugeInt(x) | Value::Decimal { unscaled: x, .. } => {
mix(mix(state, *x as u64), (*x >> 64) as u64)
}
Value::UHugeInt(x) => mix(mix(state, *x as u64), (*x >> 64) as u64),
Value::Interval { months, days, micros } => {
let length = interval_micros(*months, *days, *micros);
mix(mix(state, length as u64), (length >> 64) as u64)
}
other => mix(state, bytes_word(other.to_string().as_bytes())),
}
}
fn bytes_word(bytes: &[u8]) -> u64 {
let mut state = 0u64;
let mut words = bytes.chunks_exact(8);
for word in &mut words {
state = mix(state, u64::from_le_bytes(word.try_into().unwrap_or([0; 8])));
}
let rest = words.remainder();
if !rest.is_empty() {
let mut last = [0; 8];
last[..rest.len()].copy_from_slice(rest);
state = mix(state, u64::from_le_bytes(last));
}
mix(state, bytes.len() as u64)
}
#[cfg(test)]
mod tests {
use rudb_common::LogicalType;
use super::*;
#[test]
fn a_stored_group_key_is_narrower_than_a_general_recursive_value() {
assert!(size_of::<Stored>() < size_of::<Value>());
}
fn hashed(column: &Vector) -> Vec<u64> {
let mut hashes = Vec::new();
hash(std::slice::from_ref(column), column.len(), &mut hashes);
hashes
}
fn flat(ty: LogicalType, values: &[Value]) -> Vector {
Vector::from_values(ty, values).expect("a flat vector of these values")
}
#[test]
fn a_dictionary_hashes_the_same_as_the_flat_column_it_stands_for() {
let long = "lovelace, and a string past the sixteen bytes a view holds inline";
let values = [
Value::Varchar("ada".into()),
Value::Varchar(String::new()),
Value::Null,
Value::Varchar(long.into()),
];
let plain = flat(LogicalType::Varchar, &values);
let distinct = flat(LogicalType::Varchar, &values);
let dictionary =
Vector::dictionary(vec![0, 1, 2, 3], distinct).expect("a dictionary of those values");
assert_eq!(hashed(&plain), hashed(&dictionary));
}
#[test]
fn a_constant_and_a_sequence_hash_the_same_as_the_values_they_stand_for() {
let constant = Vector::constant(LogicalType::Integer, Value::Integer(7), 3);
let plain = flat(LogicalType::Integer, &vec![Value::Integer(7); 3]);
assert_eq!(hashed(&constant), hashed(&plain));
let sequence = Vector::sequence(10, 2, 4);
let counted = flat(
LogicalType::BigInt,
&[Value::BigInt(10), Value::BigInt(12), Value::BigInt(14), Value::BigInt(16)],
);
assert_eq!(hashed(&sequence), hashed(&counted));
}
#[test]
fn the_floats_that_group_together_hash_together() {
let left = flat(LogicalType::Double, &[Value::Double(f64::NAN), Value::Double(0.0)]);
let right = flat(LogicalType::Double, &[Value::Double(-f64::NAN), Value::Double(-0.0)]);
assert_eq!(hashed(&left), hashed(&right));
}
#[test]
fn a_null_does_not_hash_as_a_zero() {
let nulls = flat(LogicalType::BigInt, &[Value::Null]);
let zeroes = flat(LogicalType::BigInt, &[Value::BigInt(0)]);
assert_ne!(hashed(&nulls), hashed(&zeroes));
}
#[test]
fn the_same_values_in_a_different_order_hash_apart() {
let ones = flat(LogicalType::Integer, &[Value::Integer(1)]);
let twos = flat(LogicalType::Integer, &[Value::Integer(2)]);
let mut forwards = Vec::new();
let mut backwards = Vec::new();
hash(&[ones.clone(), twos.clone()], 1, &mut forwards);
hash(&[twos, ones], 1, &mut backwards);
assert_ne!(forwards, backwards);
}
#[test]
fn a_key_that_has_been_seen_is_found_and_a_new_one_is_not() {
let names = flat(
LogicalType::Varchar,
&[Value::Varchar("ada".into()), Value::Varchar("ada".into()), Value::Null],
);
let numbers =
flat(LogicalType::Integer, &[Value::Integer(1), Value::Integer(1), Value::Integer(1)]);
let keys = [names, numbers];
let mut hashes = Vec::new();
hash(&keys, 3, &mut hashes);
let mut table = Table::new(&[LogicalType::Varchar, LogicalType::Integer]);
let Probe::Vacant(bucket) = table.probe(hashes[0], &keys, 0) else {
panic!("an empty table found a group");
};
let slot = table.insert(bucket, hashes[0], &keys, 0).expect("room for one group");
assert!(matches!(table.probe(hashes[1], &keys, 1), Probe::Found(found) if found == slot));
assert!(matches!(table.probe(hashes[2], &keys, 2), Probe::Vacant(_)));
assert_eq!(table.len(), 1);
}
#[test]
fn a_group_is_found_again_when_its_string_key_arrives_as_a_constant() {
let long = "lovelace, and a string past the sixteen bytes a view holds inline";
for text in ["ada", "", long] {
let names = Vector::constant(LogicalType::Varchar, Value::Varchar(text.into()), 2);
let keys = [names];
let mut hashes = Vec::new();
hash(&keys, 2, &mut hashes);
let mut table = Table::new(&[LogicalType::Varchar]);
let Probe::Vacant(bucket) = table.probe(hashes[0], &keys, 0) else {
panic!("an empty table found a group");
};
let slot = table.insert(bucket, hashes[0], &keys, 0).expect("room for one group");
assert!(
matches!(table.probe(hashes[1], &keys, 1), Probe::Found(found) if found == slot),
"{text:?} did not find itself"
);
assert_eq!(table.len(), 1);
}
}
#[test]
fn two_constants_of_different_strings_are_still_two_groups() {
let ada = Vector::constant(LogicalType::Varchar, Value::Varchar("ada".into()), 1);
let grace = Vector::constant(LogicalType::Varchar, Value::Varchar("grace".into()), 1);
let mut first = Vec::new();
let mut second = Vec::new();
hash(std::slice::from_ref(&ada), 1, &mut first);
hash(std::slice::from_ref(&grace), 1, &mut second);
let mut table = Table::new(&[LogicalType::Varchar]);
let keys = [ada];
let Probe::Vacant(bucket) = table.probe(first[0], &keys, 0) else {
panic!("an empty table found a group");
};
table.insert(bucket, first[0], &keys, 0).expect("room for one group");
assert!(matches!(table.probe(second[0], &[grace], 0), Probe::Vacant(_)));
}
#[test]
fn every_group_is_still_found_after_the_buckets_have_doubled() {
let values: Vec<Value> = (0..1000).map(Value::BigInt).collect();
let column = flat(LogicalType::BigInt, &values);
let keys = [column];
let mut hashes = Vec::new();
hash(&keys, values.len(), &mut hashes);
let mut table = Table::new(&[LogicalType::BigInt]);
for (row, &one) in hashes.iter().enumerate() {
let Probe::Vacant(bucket) = table.probe(one, &keys, row) else {
panic!("row {row} was found before it was inserted");
};
let slot = table.insert(bucket, one, &keys, row).expect("room");
assert_eq!(slot, row);
}
assert_eq!(table.len(), values.len());
for (row, &one) in hashes.iter().enumerate() {
assert!(
matches!(table.probe(one, &keys, row), Probe::Found(slot) if slot == row),
"row {row} was lost by a rehash"
);
}
let column = table.column(0, 0..values.len());
assert_eq!(column.len(), values.len());
assert_eq!(column[7], Value::BigInt(7));
}
#[test]
fn common_numeric_keys_keep_their_physical_width() {
let values: Vec<Value> = (0..1000).map(Value::BigInt).collect();
let keys = [flat(LogicalType::BigInt, &values)];
let mut hashes = Vec::new();
hash(&keys, values.len(), &mut hashes);
let mut table = Table::new(&[LogicalType::BigInt]);
for (row, &hash) in hashes.iter().enumerate() {
let Probe::Vacant(bucket) = table.probe(hash, &keys, row) else {
panic!("a unique key was already present");
};
table.insert(bucket, hash, &keys, row).expect("room for the group");
}
let key_bytes = table.columns[0].footprint();
assert!(
key_bytes < values.len() * 9,
"{key_bytes} bytes stored a thousand eight-byte keys and their validity"
);
}
#[test]
fn string_key_bytes_are_counted_in_the_column() {
let long = "a string well past the sixteen bytes a view holds inline".to_string();
let column = flat(LogicalType::Varchar, &[Value::Varchar(long.clone())]);
let keys = [column];
let mut hashes = Vec::new();
hash(&keys, 1, &mut hashes);
let mut table = Table::new(&[LogicalType::Varchar]);
assert_eq!(table.owned(), 0);
let Probe::Vacant(bucket) = table.probe(hashes[0], &keys, 0) else {
panic!("an empty table found a group");
};
table.insert(bucket, hashes[0], &keys, 0).expect("room");
assert!(
table.footprint() >= long.len() as u64,
"{} bytes do not include the string",
table.footprint()
);
}
}