use rudb_common::bounds::Bound;
use rudb_common::{Error, Result, Value, interval_micros};
use rudb_kernels::NOWHERE;
use rudb_vector::{Data, Packed, Vector};
use std::sync::Arc;
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;
pub(crate) const BATCH: usize = 64;
const HOT: usize = 8 * 1024;
const NOTHING: u64 = 0x9e37_79b9_7f4a_7c15;
const VACANT: u64 = EMPTY as u64;
fn salt_of(hash: u64) -> u32 {
(hash >> 32) as u32
}
fn bucket_of(salt: u32, slot: usize) -> u64 {
(u64::from(salt) << 32) | slot as u64
}
fn slot_of(bucket: u64) -> u32 {
bucket as u32
}
fn bucket_salt(bucket: u64) -> u32 {
(bucket >> 32) as u32
}
#[derive(Debug)]
pub(crate) struct Table {
buckets: Vec<u64>,
columns: Vec<Column>,
hashes: Vec<u64>,
owned: u64,
direct: Option<Direct>,
}
#[derive(Debug)]
struct Direct {
cells: Vec<u32>,
base: i128,
}
impl Direct {
fn cell(&self, keys: &[Vector], row: usize) -> Option<usize> {
let [column] = keys else { return None };
if !column.validity().is_valid(row) {
return Some(0);
}
let Bound::Int(value) = Bound::of_value(&column.value_at(row))? else {
return None;
};
self.of(value)
}
fn of(&self, value: i128) -> Option<usize> {
let step = value.checked_sub(self.base)?.checked_add(1)?;
usize::try_from(step).ok().filter(|&cell| cell < self.cells.len())
}
fn footprint(&self) -> usize {
self.cells.capacity() * size_of::<u32>()
}
}
fn addressable(ty: &rudb_common::LogicalType) -> bool {
use rudb_common::LogicalType as Type;
matches!(
ty,
Type::Boolean
| Type::TinyInt
| Type::SmallInt
| Type::Integer
| Type::BigInt
| Type::UTinyInt
| Type::USmallInt
| Type::UInteger
| Type::UBigInt
| Type::Date
)
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Probe {
Found(usize),
Vacant(usize),
}
impl Table {
pub(crate) fn new(types: &[rudb_common::LogicalType]) -> Self {
Self::sized(types, FIRST)
}
pub(crate) fn with_groups(types: &[rudb_common::LogicalType], groups: u64) -> Self {
Self::sized(types, Self::buckets_for(groups))
}
pub(crate) fn room(groups: u64) -> u64 {
u64::try_from(Self::buckets_for(groups) * size_of::<u64>()).unwrap_or(u64::MAX)
}
fn buckets_for(groups: u64) -> usize {
let wanted = usize::try_from(groups.saturating_mul(2)).unwrap_or(usize::MAX);
wanted.clamp(FIRST, LIMIT / 2).next_power_of_two()
}
fn sized(types: &[rudb_common::LogicalType], buckets: usize) -> Self {
Self {
buckets: vec![VACANT; buckets],
columns: types.iter().map(Column::new).collect(),
hashes: Vec::new(),
owned: 0,
direct: None,
}
}
pub(crate) fn over_range(
mut self,
low: i128,
values: u64,
ty: &rudb_common::LogicalType,
) -> Self {
if self.columns.len() != 1 || !addressable(ty) {
return self;
}
let Ok(cells) = usize::try_from(values.saturating_add(1)) else {
return self;
};
self.direct = Some(Direct { cells: vec![EMPTY; cells], base: low });
self
}
pub(crate) fn len(&self) -> usize {
self.hashes.len()
}
pub(crate) fn hash_of(&self, slot: usize) -> u64 {
self.hashes[slot]
}
pub(crate) fn owned(&self) -> u64 {
self.owned
}
pub(crate) fn footprint(&self) -> u64 {
let buckets = self.buckets.capacity() * size_of::<u64>();
let hashes = self.hashes.capacity() * size_of::<u64>();
let keys: usize = self.columns.iter().map(Column::footprint).sum();
let direct = self.direct.as_ref().map_or(0, Direct::footprint);
u64::try_from(buckets + hashes + keys + direct).unwrap_or(u64::MAX)
}
pub(crate) fn probe(&self, hash: u64, keys: &[Vector], row: usize) -> Probe {
let mask = self.buckets.len() - 1;
let salt = salt_of(hash);
let mut at = (hash as usize) & mask;
loop {
let bucket = self.buckets[at];
let slot = slot_of(bucket);
if slot == EMPTY {
return Probe::Vacant(at);
}
let slot = slot as usize;
if bucket_salt(bucket) == salt && self.holds(slot, keys, row) {
return Probe::Found(slot);
}
at = (at + 1) & mask;
}
}
pub(crate) fn probe_run(
&self,
hashes: &[u64],
keys: &[Vector],
from: usize,
upto: usize,
slots: &mut [usize],
walk: &mut Walk,
) {
self.probe_at(hashes, keys, Rows::Run(from, upto), &mut slots[from..upto], walk);
for place in &mut walk.pending {
*place += from;
}
}
pub(crate) fn probe_these(
&self,
hashes: &[u64],
keys: &[Vector],
rows: &[usize],
slots: &mut [usize],
walk: &mut Walk,
) {
self.probe_at(hashes, keys, Rows::These(rows), slots, walk);
}
fn probe_at(
&self,
hashes: &[u64],
keys: &[Vector],
rows: Rows<'_>,
slots: &mut [usize],
walk: &mut Walk,
) {
let mask = self.buckets.len() - 1;
walk.pending.clear();
if self.direct_at(keys, rows, slots, walk) {
return;
}
if self.buckets.len() <= HOT {
if let [column] = keys
&& self.hot_one(hashes, column, rows, slots, walk)
{
return;
}
for (out, found) in slots.iter_mut().enumerate().take(rows.len()) {
let row = rows.at(out);
match self.probe(hashes[row], keys, row) {
Probe::Found(slot) => *found = slot,
Probe::Vacant(_) => walk.pending.push(out),
}
}
return;
}
walk.here.clear();
walk.here.extend((0..rows.len()).map(|out| {
let row = rows.at(out);
Step { row, out, at: (hashes[row] as usize) & mask }
}));
while !walk.here.is_empty() {
walk.seen.clear();
walk.seen.extend(walk.here.iter().map(|step| self.buckets[step.at]));
walk.same.clear();
walk.same.extend(walk.here.iter().zip(&walk.seen).map(|(step, &bucket)| {
slot_of(bucket) != EMPTY && bucket_salt(bucket) == salt_of(hashes[step.row])
}));
for (at, column) in keys.iter().enumerate() {
self.columns[at].holds_run(&walk.here, &walk.seen, column, &mut walk.same);
}
walk.next.clear();
for ((step, &bucket), &same) in walk.here.iter().zip(&walk.seen).zip(&walk.same) {
let slot = slot_of(bucket);
if slot == EMPTY {
walk.pending.push(step.out);
} else if same {
slots[step.out] = slot as usize;
} else {
walk.next.push(Step { row: step.row, out: step.out, at: (step.at + 1) & mask });
}
}
std::mem::swap(&mut walk.here, &mut walk.next);
}
walk.pending.sort_unstable();
}
fn direct_at(
&self,
keys: &[Vector],
rows: Rows<'_>,
slots: &mut [usize],
walk: &mut Walk,
) -> bool {
let Some(direct) = self.direct.as_ref() else { return false };
let [column] = keys else { return false };
let Some(data) = column.data() else { return false };
let validity = column.validity();
walk.cells.clear();
macro_rules! cells {
($values:expr) => {{
let values = $values.as_slice();
for out in 0..rows.len() {
let row = rows.at(out);
let cell = if !validity.is_valid(row) {
0
} else {
let Some(&value) = values.get(row) else { return false };
let Some(cell) = direct.of(i128::from(value)) else { return false };
cell
};
walk.cells.push(direct.cells[cell]);
}
}};
}
match data {
Data::Int8(values) => cells!(values),
Data::Int16(values) => cells!(values),
Data::Int32(values) => cells!(values),
Data::Int64(values) => cells!(values),
Data::UInt8(values) => cells!(values),
Data::UInt16(values) => cells!(values),
Data::UInt32(values) => cells!(values),
Data::UInt64(values) => cells!(values),
_ => return false,
}
for (out, &slot) in walk.cells.iter().enumerate() {
if slot == EMPTY {
walk.pending.push(out);
} else {
slots[out] = slot as usize;
}
}
true
}
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() {
self.owned += self.columns[at].push_from(column, row)?;
}
self.hashes.push(hash);
self.buckets[bucket] = bucket_of(salt_of(hash), slot);
let cell = self.direct.as_ref().and_then(|direct| direct.cell(keys, row));
if let (Some(direct), Some(cell)) = (self.direct.as_mut(), cell) {
direct.cells[cell] = u32::try_from(slot).unwrap_or(EMPTY);
}
if self.hashes.len() * 2 >= self.buckets.len() {
self.regrow();
}
Ok(slot)
}
pub(crate) fn append(&mut self, 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() {
self.owned += self.columns[at].push_from(column, row)?;
}
self.hashes.push(0);
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 hot_one(
&self,
hashes: &[u64],
column: &Vector,
rows: Rows<'_>,
slots: &mut [usize],
walk: &mut Walk,
) -> bool {
let Some(data) = column.data() else { return false };
if column.validity().has_nulls(column.len()) {
return false;
}
let stored = &self.columns[0];
let mask = self.buckets.len() - 1;
macro_rules! walk {
($stored:expr, $values:expr, $widen:expr) => {{
let (held, values) = ($stored, $values.as_slice());
for (out, found) in slots.iter_mut().enumerate().take(rows.len()) {
let row = rows.at(out);
let hash = hashes[row];
let salt = salt_of(hash);
let mut at = (hash as usize) & mask;
loop {
let bucket = self.buckets[at];
let slot = slot_of(bucket);
if slot == EMPTY {
walk.pending.push(out);
break;
}
let slot = slot as usize;
if bucket_salt(bucket) == salt
&& match values.get(row) {
Some(&value) => stored.valid[slot] && $widen(value) == held[slot],
None => stored.holds(slot, column, row),
}
{
*found = slot;
break;
}
at = (at + 1) & mask;
}
}
true
}};
}
match (&stored.data, data) {
(StoredData::TinyInt(held), Data::Int8(values)) => walk!(held, values, |v| v),
(StoredData::SmallInt(held), Data::Int16(values)) => walk!(held, values, |v| v),
(StoredData::Integer(held), Data::Int32(values)) => walk!(held, values, |v| v),
(StoredData::BigInt(held), Data::Int64(values)) => walk!(held, values, |v| v),
(StoredData::Wide { values: held, .. }, Data::Int128(values)) => {
walk!(held, values, |v| v)
}
(StoredData::Wide { values: held, .. }, Data::Int64(values)) => {
walk!(held, values, i128::from)
}
(StoredData::Wide { values: held, .. }, Data::Int32(values)) => {
walk!(held, values, i128::from)
}
(StoredData::Wide { values: held, .. }, Data::Int16(values)) => {
walk!(held, values, i128::from)
}
_ => false,
}
}
fn regrow(&mut self) {
let mut buckets = vec![VACANT; 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 slot_of(buckets[at]) != EMPTY {
at = (at + 1) & mask;
}
buckets[at] = bucket_of(salt_of(hash), slot);
}
self.buckets = buckets;
}
pub(crate) fn column(
&self,
at: usize,
ty: &rudb_common::LogicalType,
range: std::ops::Range<usize>,
) -> Result<Vector> {
self.columns[at].vector(ty, range)
}
pub(crate) fn column_slots(
&self,
at: usize,
ty: &rudb_common::LogicalType,
slots: &[usize],
) -> Result<Vector> {
self.columns[at].vector_at(ty, slots)
}
}
#[derive(Debug, Clone, Copy)]
struct Step {
row: usize,
out: usize,
at: usize,
}
#[derive(Debug, Clone, Copy)]
enum Rows<'a> {
Run(usize, usize),
These(&'a [usize]),
}
impl Rows<'_> {
fn len(&self) -> usize {
match *self {
Rows::Run(from, upto) => upto - from,
Rows::These(rows) => rows.len(),
}
}
fn at(&self, index: usize) -> usize {
match *self {
Rows::Run(from, _) => from + index,
Rows::These(rows) => rows[index],
}
}
}
#[derive(Debug, Default)]
pub(crate) struct Walk {
here: Vec<Step>,
next: Vec<Step>,
seen: Vec<u64>,
same: Vec<bool>,
pending: Vec<usize>,
cells: Vec<u32>,
}
impl Walk {
pub(crate) fn pending(&self) -> &[usize] {
&self.pending
}
}
const KEYS: usize = 4;
const COMBOS: usize = 2048;
const WIDE_COMBOS: usize = 1 << 18;
#[derive(Debug, Clone, Copy)]
enum Places<'a> {
Codes {
codes: &'a [u32],
values: &'a Arc<Vector>,
},
Bits {
packed: Packed<'a>,
},
CodedBits {
at: &'a [u32],
packed: Packed<'a>,
},
Values {
values: &'a [i64],
low: i64,
runs: &'a [(i64, usize)],
},
}
#[derive(Debug, Default)]
pub(crate) struct Widened {
values: Vec<Vec<i64>>,
runs: Vec<Vec<(i64, usize)>>,
}
#[derive(Debug, Clone)]
pub(crate) enum Origin {
Dictionary(Arc<Vector>),
Bits(i128, u32),
Window(i64, usize),
}
#[derive(Debug, Clone, Copy)]
struct CodedColumn<'a> {
places: Places<'a>,
stride: usize,
nothing: usize,
nullable: bool,
column: &'a Vector,
}
impl CodedColumn<'_> {
fn add_into(&self, into: &mut [usize]) {
let stride = self.stride;
if self.nullable {
let nothing = self.nothing;
let column = self.column;
match self.places {
Places::Codes { codes, .. } => {
for (row, place) in into.iter_mut().enumerate() {
let code =
if column.is_null_at(row) { nothing } else { codes[row] as usize };
*place += code * stride;
}
}
Places::Bits { packed } => {
for (row, place) in into.iter_mut().enumerate() {
let code = if column.is_null_at(row) {
nothing
} else {
packed.code(row) as usize
};
*place += code * stride;
}
}
Places::CodedBits { at, packed } => {
for (row, place) in into.iter_mut().enumerate() {
let code = if column.is_null_at(row) {
nothing
} else {
packed.code(at[row] as usize) as usize
};
*place += code * stride;
}
}
Places::Values { values, low, .. } => {
for (row, place) in into.iter_mut().enumerate() {
let code = if column.is_null_at(row) {
nothing
} else {
values[row].wrapping_sub(low) as u64 as usize
};
*place += code * stride;
}
}
}
return;
}
match self.places {
Places::Codes { codes, .. } => {
for (row, place) in into.iter_mut().enumerate() {
*place += codes[row] as usize * stride;
}
}
Places::Bits { packed } => {
for (row, place) in into.iter_mut().enumerate() {
*place += packed.code(row) as usize * stride;
}
}
Places::CodedBits { at, packed } => {
for (row, place) in into.iter_mut().enumerate() {
*place += packed.code(at[row] as usize) as usize * stride;
}
}
Places::Values { values, low, .. } => {
for (place, &value) in into.iter_mut().zip(values) {
*place += value.wrapping_sub(low) as u64 as usize * stride;
}
}
}
}
}
pub(crate) struct Coded<'a> {
columns: [Option<CodedColumn<'a>>; KEYS],
combos: usize,
}
impl<'a> Coded<'a> {
pub(crate) fn combos(&self) -> usize {
self.combos
}
pub(crate) fn by_value(&self) -> bool {
self.columns.iter().flatten().all(|column| matches!(column.places, Places::Values { .. }))
}
pub(crate) fn reads_values(&self) -> bool {
self.columns.iter().flatten().any(|column| matches!(column.places, Places::Values { .. }))
}
pub(crate) fn hash_of(&self, row: usize) -> u64 {
let mut state = 0;
for column in self.columns.iter().flatten() {
let word = match column.places {
_ if column.nullable && column.column.is_null_at(row) => NOTHING,
Places::Values { values, .. } => values[row] as u64,
_ => NOTHING,
};
state = mix(state, word);
}
spread(state)
}
pub(crate) fn places(&self, rows: usize, places: &mut Vec<usize>) {
places.clear();
places.resize(rows, 0);
for column in self.columns.iter().flatten() {
column.add_into(places);
}
}
pub(crate) fn look_up(&self, map: &[u32], slots: &mut [usize]) -> Option<bool> {
let mut plain = self.columns.iter().flatten().map(|column| match column.places {
Places::Codes { codes, .. } if !column.nullable => Some((codes, column.stride)),
_ => None,
});
let (first, stride) = plain.next()??;
let second = plain.next();
if plain.next().is_some() {
return None;
}
let mut missed = false;
match second {
None => {
for (slot, &code) in slots.iter_mut().zip(first) {
let found = map[code as usize * stride];
missed |= found == UNSEEN;
*slot = slot_at(found);
}
}
Some(second) => {
let (other, across) = second?;
for ((slot, &code), &next) in slots.iter_mut().zip(first).zip(other) {
let found = map[code as usize * stride + next as usize * across];
missed |= found == UNSEEN;
*slot = slot_at(found);
}
}
}
Some(missed)
}
pub(crate) fn place_runs(
&self,
rows: usize,
most: usize,
into: &mut Vec<(usize, usize)>,
) -> bool {
into.clear();
let mut columns = self.columns.iter().flatten();
let (Some(column), None) = (columns.next(), columns.next()) else {
return false;
};
if column.nullable {
return false;
}
let stride = column.stride;
match column.places {
Places::Values { runs, low, .. } if runs.last().is_some_and(|run| run.1 == rows) => {
if runs.len() > most + 1 {
return false;
}
let place = |value: i64| value.wrapping_sub(low) as u64 as usize * stride;
into.extend(runs.iter().map(|&(value, end)| (place(value), end)));
true
}
Places::Values { values, low, .. } => values.get(..rows).is_some_and(|values| {
runs_in(values, most, into, |value| {
value.wrapping_sub(low) as u64 as usize * stride
})
}),
Places::Codes { codes, .. } => codes
.get(..rows)
.is_some_and(|codes| runs_in(codes, most, into, |code| code as usize * stride)),
_ => false,
}
}
pub(crate) fn same_as(&self, held: &[Origin]) -> bool {
let mut at = 0;
for column in self.columns.iter().flatten() {
let same = match (held.get(at), column.places) {
(Some(Origin::Dictionary(dictionary)), Places::Codes { values, .. }) => {
Arc::ptr_eq(dictionary, values)
}
(
Some(Origin::Bits(base, width)),
Places::Bits { packed } | Places::CodedBits { packed, .. },
) => *base == packed.base() && *width == packed.width(),
(Some(Origin::Window(bottom, span)), Places::Values { low, .. }) => {
*bottom == low && *span == column.nothing + 1
}
_ => false,
};
if !same {
return false;
}
at += 1;
}
at == held.len()
}
pub(crate) fn grows(&self, held: &[Origin], built: usize) -> Option<usize> {
let [Some(column), rest @ ..] = &self.columns else {
return None;
};
if rest.iter().any(Option::is_some) {
return None;
}
match (held, column.places) {
([Origin::Window(bottom, span)], Places::Values { low, .. })
if *bottom == low && *span == built && *span < column.nothing + 1 =>
{
Some(*span)
}
_ => None,
}
}
pub(crate) fn hold(&self, into: &mut Vec<Origin>) {
into.clear();
for column in self.columns.iter().flatten() {
into.push(match column.places {
Places::Codes { values, .. } => Origin::Dictionary(Arc::clone(values)),
Places::Bits { packed } | Places::CodedBits { packed, .. } => {
Origin::Bits(packed.base(), packed.width())
}
Places::Values { low, .. } => Origin::Window(low, column.nothing + 1),
});
}
}
}
#[cfg(test)]
pub(crate) fn coded<'a>(keys: &'a [Vector], rows: usize) -> Option<Coded<'a>> {
coded_within(keys, rows, &[], None)
}
type KeyRuns = Vec<(i64, usize)>;
pub(crate) fn coded_within<'a>(
keys: &'a [Vector],
rows: usize,
held: &[Origin],
widened: Option<&'a mut Widened>,
) -> Option<Coded<'a>> {
if keys.is_empty() || keys.len() > KEYS {
return None;
}
let room = if keys.len() == 1 { WIDE_COMBOS } else { COMBOS };
let mut found = [None; KEYS];
let mut fallback = [None; KEYS];
let mut taken: usize = 1;
let mut wanting = 0;
for (at, key) in keys.iter().enumerate() {
match places_of(key, rows, room) {
Some(read) if widened.is_some() && moved_off(&read.0, held.get(at)) => {
fallback[at] = Some(read);
wanting += 1;
}
Some(read) => {
taken = taken.checked_mul(read.1).filter(|&taken| taken <= room)?;
found[at] = Some(read);
}
None => wanting += 1,
}
}
let mut windows = [None; KEYS];
let (values, runs): (&'a [Vec<i64>], &'a [KeyRuns]) = if wanting == 0 {
(&[], &[])
} else {
let Widened { values, runs } = widened?;
values.resize_with(keys.len(), Vec::new);
runs.resize_with(keys.len(), Vec::new);
for (at, key) in keys.iter().enumerate() {
if found[at].is_some() {
continue;
}
let limit = room / taken;
let into = (&mut values[at], &mut runs[at], keys.len() == 1);
let window = window_of(key, rows, held.get(at), limit, wanting == 1, into);
match (window, fallback[at]) {
(Some(window), _) => {
taken = taken.checked_mul(window.1).filter(|&taken| taken <= room)?;
windows[at] = Some(window);
}
(None, Some(read)) => {
taken = taken.checked_mul(read.1).filter(|&taken| taken <= room)?;
found[at] = Some(read);
}
(None, None) => return None,
}
}
(values, runs)
};
let mut columns = [None; KEYS];
let mut combos: usize = 1;
for (at, key) in keys.iter().enumerate() {
let (places, span, nullable) = match (found[at], windows[at]) {
(Some(read), _) => read,
(None, Some((low, span, nullable))) => {
let runs = runs.get(at).map_or(&[][..], Vec::as_slice);
let values = values.get(at)?.get(..rows)?;
(Places::Values { values, low, runs }, span, nullable)
}
(None, None) => return None,
};
if combos.checked_mul(span)? > room {
return None;
}
columns[at] = Some(CodedColumn {
places,
stride: combos,
nothing: span - 1,
nullable,
column: key,
});
combos *= span;
}
Some(Coded { columns, combos })
}
pub(crate) const UNSEEN: u32 = u32::MAX;
#[inline(always)]
pub(crate) fn slot_at(held: u32) -> usize {
if held == UNSEEN { NOWHERE } else { held as usize }
}
#[inline(always)]
pub(crate) fn held_at(slot: usize) -> u32 {
u32::try_from(slot).unwrap_or(UNSEEN)
}
pub(crate) fn fits_the_map(groups: usize, rows: usize) -> bool {
groups.saturating_add(rows) < UNSEEN as usize
}
pub(crate) fn seeded_window(
types: &[rudb_common::LogicalType],
low: i128,
values: u64,
) -> Option<(i64, usize)> {
use rudb_common::LogicalType;
let [ty] = types else { return None };
if !matches!(
ty,
LogicalType::TinyInt
| LogicalType::SmallInt
| LogicalType::Integer
| LogicalType::BigInt
| LogicalType::UTinyInt
| LogicalType::USmallInt
| LogicalType::UInteger
) {
return None;
}
let places = usize::try_from(values).ok()?.checked_add(1)?;
(places <= WIDE_COMBOS).then_some((i64::try_from(low).ok()?, places))
}
fn moved_off(places: &Places<'_>, held: Option<&Origin>) -> bool {
match (places, held) {
(Places::Bits { packed } | Places::CodedBits { packed, .. }, Some(held)) => {
!matches!(held, Origin::Bits(base, width)
if *base == packed.base() && *width == packed.width())
}
(Places::Codes { values, .. }, Some(held)) => {
!matches!(held, Origin::Dictionary(dictionary) if Arc::ptr_eq(dictionary, values))
}
_ => false,
}
}
fn window_of(
key: &Vector,
rows: usize,
held: Option<&Origin>,
limit: usize,
alone: bool,
(into, runs, keep_runs): (&mut Vec<i64>, &mut Vec<(i64, usize)>, bool),
) -> Option<(i64, usize, bool)> {
runs.clear();
let nullable = !key.none_null();
let selected = keep_runs && !nullable && selected_runs(key, rows, into, runs);
if !selected && !signed_rows(key, rows, into) {
return None;
}
let (mut lowest, mut highest) = (i64::MAX, i64::MIN);
for &(value, _) in runs.iter() {
lowest = lowest.min(value);
highest = highest.max(value);
}
let mut current = into.first().copied().unwrap_or_default();
let mut keeping = keep_runs && !nullable;
let most_runs = into.len() / 8 + 1;
let scanned: &[i64] = if selected { &[] } else { into };
for (block, values) in scanned.chunks(128).enumerate() {
if nullable {
for (row, &value) in values.iter().enumerate() {
if !key.is_null_at(block * 128 + row) {
lowest = lowest.min(value);
highest = highest.max(value);
}
}
} else {
lowest = lowest.min(current);
highest = highest.max(current);
for (at, stretch) in values.chunks(16).enumerate() {
if !stretch.iter().fold(false, |differ, &value| differ | (value != current)) {
continue;
}
if keeping {
for (row, &value) in stretch.iter().enumerate() {
if value != current {
runs.push((current, block * 128 + at * 16 + row));
current = value;
lowest = lowest.min(value);
highest = highest.max(value);
}
}
keeping = runs.len() < most_runs;
} else {
for &value in stretch {
lowest = lowest.min(value);
highest = highest.max(value);
}
current = stretch[stretch.len() - 1];
}
}
}
if lowest <= highest && (i128::from(highest) - i128::from(lowest)) >= limit as i128 {
return None;
}
}
if selected {
if (i128::from(highest) - i128::from(lowest)) >= limit as i128 {
return None;
}
} else if keeping && !into.is_empty() {
runs.push((current, into.len()));
} else {
runs.clear();
}
let kept = match held {
Some(&Origin::Window(low, span)) => Some((low, span)),
_ => None,
};
let most = i128::try_from(limit.checked_sub(1)?).ok()?;
let top_of = |low: i64, span: usize| i128::from(low) + span as i128 - 2;
if let Some((low, span)) = kept {
if lowest > highest
|| (lowest >= low && i128::from(highest) <= top_of(low, span) && span <= limit)
{
return Some((low, span, nullable));
}
}
if lowest > highest {
return Some((0, 2, nullable));
}
let (mut bottom, mut top) = (i128::from(lowest), i128::from(highest));
if let Some((low, span)) = kept {
let (wider_bottom, wider_top) = (bottom.min(i128::from(low)), top.max(top_of(low, span)));
if wider_top - wider_bottom < most {
(bottom, top) = (wider_bottom, wider_top);
}
}
let width = top - bottom + 1;
if width > most {
return None;
}
let wanted = if alone { (width * 2).max(1024).min(most) } else { width };
let climbing = kept.is_some_and(|(low, _)| i128::from(low) == bottom);
let slack_below = if climbing { 0 } else { (wanted - width) / 2 };
let low = i64::try_from(bottom - slack_below).or_else(|_| i64::try_from(bottom)).ok()?;
Some((low, usize::try_from(wanted).ok()?.checked_add(1)?, nullable))
}
#[inline(never)]
fn selected_runs(
key: &Vector,
rows: usize,
into: &mut Vec<i64>,
runs: &mut Vec<(i64, usize)>,
) -> bool {
runs.clear();
let Some((at, values)) = key.dictionary_parts() else {
return false;
};
let Some(at) = at.get(..rows) else {
return false;
};
let (Some(&first), Some(&last)) = (at.first(), at.last()) else {
return false;
};
let span = (first as usize, last as usize + 1);
if span.1.saturating_sub(span.0) < rows {
return false;
}
let climbing =
|| at.iter().zip(&at[1..]).fold(true, |up, (&before, &after)| up & (before < after));
if !values.signed_runs(span, 8, runs) || !climbing() {
runs.clear();
return false;
}
let (mut kept, mut written) = (0, 0_usize);
for read in 0..runs.len() {
let (value, end) = runs[read];
let upto = kept + below(&at[kept..], end);
if upto == kept {
continue;
}
kept = upto;
match written.checked_sub(1).map(|last| &mut runs[last]) {
Some(last) if last.0 == value => last.1 = kept,
_ => {
runs[written] = (value, kept);
written += 1;
}
}
}
runs.truncate(written);
if kept != rows || runs.len() > rows / 8 + 1 {
runs.clear();
return false;
}
into.clear();
for &(value, end) in runs.iter() {
into.resize(end, value);
}
true
}
fn below(at: &[u32], end: usize) -> usize {
let Some(&first) = at.first() else {
return 0;
};
let most = end.saturating_sub(first as usize).min(at.len());
if most == 0 || (at[most - 1] as usize) < end {
return most;
}
at[..most].partition_point(|&row| (row as usize) < end)
}
fn signed_rows(key: &Vector, rows: usize, into: &mut Vec<i64>) -> bool {
let wide = match key.logical_type() {
rudb_common::LogicalType::HugeInt | rudb_common::LogicalType::UHugeInt => true,
rudb_common::LogicalType::Decimal { width, .. } => wide_decimal(*width),
_ => false,
};
if wide {
return false;
}
if let Some((at, values)) = key.dictionary_parts() {
let Some(at) = at.get(..rows) else {
return false;
};
let Some(packed) = values.packed_parts() else {
return gathered(at, values, into);
};
let Ok(base) = i64::try_from(packed.base()) else {
return false;
};
if !rudb_vector::below(at, values.len()) {
return false;
}
into.clear();
into.extend(at.iter().map(|&row| base.wrapping_add(packed.code(row as usize) as i64)));
return true;
}
if key.len() < rows || !key.signed_block(into) {
return false;
}
into.truncate(rows);
true
}
fn runs_in<T: Copy + Eq>(
values: &[T],
most: usize,
into: &mut Vec<(usize, usize)>,
place: impl Fn(T) -> usize,
) -> bool {
let Some(&first) = values.first() else {
return false;
};
let mut current = first;
let mut row = 0;
while row < values.len() {
let end = (row + 16).min(values.len());
let block = &values[row..end];
if block.iter().fold(false, |differ, &value| differ | (value != current)) {
for (at, &value) in block.iter().enumerate() {
if value != current {
if into.len() >= most {
into.clear();
return false;
}
into.push((place(current), row + at));
current = value;
}
}
}
row = end;
}
into.push((place(current), values.len()));
true
}
fn gathered(at: &[u32], values: &Vector, into: &mut Vec<i64>) -> bool {
if values.signed_gather(at, into) {
return true;
}
if at.iter().enumerate().any(|(row, &code)| (code as usize) < row) {
return false;
}
if !values.signed_block(into) || !rudb_vector::below(at, into.len()) {
into.clear();
return false;
}
for (row, &code) in at.iter().enumerate() {
into[row] = into[code as usize];
}
into.truncate(at.len());
true
}
fn places_of(key: &Vector, rows: usize, room: usize) -> Option<(Places<'_>, usize, bool)> {
if let Some((at, values)) = key.dictionary_parts()
&& let Some(packed) = values.packed_parts()
{
let at = at.get(..rows)?;
let span = 1_usize.checked_shl(packed.width())?.checked_add(1)?;
if span > room {
return None;
}
let nullable = key.validity().has_nulls(rows) || values.validity().has_nulls(values.len());
return Some((Places::CodedBits { at, packed }, span, nullable));
}
if let Some((codes, values)) = key.shared_dictionary_parts() {
let codes = codes.get(..rows)?;
let span = values.len().checked_add(1)?;
if span > room {
return None;
}
let nullable = if key.never_null() {
false
} else if values.len() > COMBOS {
return None;
} else {
key.validity().has_nulls(rows) || (0..values.len()).any(|at| values.is_null_at(at))
};
return Some((Places::Codes { codes, values }, span, nullable));
}
let packed = key.packed_parts()?;
if key.len() < rows {
return None;
}
let span = 1_usize.checked_shl(packed.width()).filter(|&span| span <= COMBOS)?;
Some((Places::Bits { packed }, span.checked_add(1)?, key.validity().has_nulls(rows)))
}
#[derive(Debug)]
struct Column {
valid: Vec<bool>,
data: StoredData,
}
#[derive(Debug)]
enum StoredData {
TinyInt(Vec<i8>),
SmallInt(Vec<i16>),
Integer(Vec<i32>),
BigInt(Vec<i64>),
Wide {
ty: rudb_common::LogicalType,
values: Vec<i128>,
},
Varchar(StringColumn),
StableText {
dictionary: Arc<Vector>,
codes: Vec<u32>,
},
Other(Vec<Stored>),
}
fn one_integer(ty: &rudb_common::LogicalType) -> bool {
matches!(
ty,
rudb_common::LogicalType::HugeInt
| rudb_common::LogicalType::Decimal { .. }
| rudb_common::LogicalType::Date
| rudb_common::LogicalType::Time
| rudb_common::LogicalType::Timestamp
)
}
fn one_integer_of(value: &Value) -> Option<i128> {
match *value {
Value::HugeInt(held) | Value::Decimal { unscaled: held, .. } => Some(held),
Value::Date(days) => Some(i128::from(days)),
Value::Time(micros) | Value::Timestamp(micros) => Some(i128::from(micros)),
_ => None,
}
}
fn one_integer_as(ty: &rudb_common::LogicalType, held: i128) -> Value {
match ty {
rudb_common::LogicalType::Date => Value::Date(i32::try_from(held).unwrap_or(i32::MAX)),
rudb_common::LogicalType::Time => Value::Time(i64::try_from(held).unwrap_or(i64::MAX)),
rudb_common::LogicalType::Timestamp => {
Value::Timestamp(i64::try_from(held).unwrap_or(i64::MAX))
}
rudb_common::LogicalType::Decimal { width, scale } => {
Value::Decimal { unscaled: held, width: *width, scale: *scale }
}
_ => Value::HugeInt(held),
}
}
impl Column {
fn new(ty: &rudb_common::LogicalType) -> Self {
let data = match ty {
rudb_common::LogicalType::TinyInt => StoredData::TinyInt(Vec::new()),
rudb_common::LogicalType::SmallInt => StoredData::SmallInt(Vec::new()),
rudb_common::LogicalType::Integer => StoredData::Integer(Vec::new()),
rudb_common::LogicalType::BigInt => StoredData::BigInt(Vec::new()),
rudb_common::LogicalType::Varchar => StoredData::Varchar(StringColumn::default()),
ty if one_integer(ty) => StoredData::Wide { ty: ty.clone(), values: Vec::new() },
_ => StoredData::Other(Vec::new()),
};
Self { valid: Vec::new(), data }
}
fn push(&mut self, value: Value) -> Result<()> {
self.end_the_run()?;
let present = !matches!(value, Value::Null);
match (&mut self.data, value) {
(StoredData::TinyInt(values), Value::TinyInt(value)) => values.push(value),
(StoredData::TinyInt(values), Value::Null) => values.push(0),
(StoredData::SmallInt(values), Value::SmallInt(value)) => values.push(value),
(StoredData::SmallInt(values), Value::Null) => values.push(0),
(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::Wide { values, .. }, Value::Null) => values.push(0),
(StoredData::Wide { values, .. }, value) => match one_integer_of(&value) {
Some(held) => values.push(held),
None => {
return Err(Error::internal(format!(
"a group key column was given a value of the wrong type: {value:?}"
)));
}
},
(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 end_the_run(&mut self) -> Result<()> {
let StoredData::StableText { dictionary, codes } = &self.data else {
return Ok(());
};
let mut owned = StringColumn::default();
for (slot, &code) in codes.iter().enumerate() {
let bytes = match self.valid.get(slot) {
Some(true) => dictionary.bytes_at(code as usize).unwrap_or_default(),
_ => Default::default(),
};
owned.push(bytes)?;
}
self.data = StoredData::Varchar(owned);
Ok(())
}
fn push_from(&mut self, column: &Vector, row: usize) -> Result<u64> {
if matches!(&self.data, StoredData::Varchar(values) if values.ends.is_empty())
&& let Some((codes, dictionary)) = column.stable_dictionary_parts()
{
let code = *codes
.get(row)
.ok_or_else(|| Error::internal("a stable dictionary row is missing"))?;
self.data =
StoredData::StableText { dictionary: Arc::clone(dictionary), codes: vec![code] };
self.valid.push(!column.is_null_at(row));
return Ok(0);
}
if column.is_null_at(row) {
if let StoredData::StableText { dictionary, codes } = &mut self.data
&& let Some((incoming, values)) = column.stable_dictionary_parts()
&& Arc::ptr_eq(dictionary, values)
{
let code = *incoming
.get(row)
.ok_or_else(|| Error::internal("a stable dictionary row is missing"))?;
codes.push(code);
self.valid.push(false);
return Ok(0);
}
return self.push(Value::Null).map(|()| 0);
}
macro_rules! signed {
($values:expr, $width:ty) => {
match column.signed_at(row).and_then(|value| <$width>::try_from(value).ok()) {
Some(value) => {
$values.push(value);
true
}
None => false,
}
};
}
let taken = match &mut self.data {
StoredData::TinyInt(values) => signed!(values, i8),
StoredData::SmallInt(values) => signed!(values, i16),
StoredData::Integer(values) => signed!(values, i32),
StoredData::BigInt(values) => signed!(values, i64),
StoredData::Wide { values, .. } => match column.signed_at(row) {
Some(value) => {
values.push(value);
true
}
None => false,
},
StoredData::Varchar(values) => match column.bytes_at(row) {
Some(bytes) => {
values.push(bytes)?;
true
}
None => false,
},
StoredData::StableText { dictionary, codes } => {
match column.stable_dictionary_parts() {
Some((incoming, values)) if Arc::ptr_eq(dictionary, values) => {
codes.push(incoming[row]);
true
}
_ => false,
}
}
StoredData::Other(_) => false,
};
if taken {
self.valid.push(true);
return Ok(0);
}
let value = column.value_at(row);
let owned = rows::owned(&value);
self.push(value)?;
Ok(if self.stores_payload() { 0 } else { owned })
}
fn footprint(&self) -> usize {
let values = match &self.data {
StoredData::TinyInt(values) => values.capacity(),
StoredData::SmallInt(values) => values.capacity() * size_of::<i16>(),
StoredData::Integer(values) => values.capacity() * size_of::<i32>(),
StoredData::BigInt(values) => values.capacity() * size_of::<i64>(),
StoredData::Wide { values, .. } => values.capacity() * size_of::<i128>(),
StoredData::Varchar(values) => values.footprint(),
StoredData::StableText { codes, .. } => codes.capacity() * size_of::<u32>(),
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.is_null_at(row);
}
match &self.data {
StoredData::TinyInt(values) => match column.signed_at(row) {
Some(value) => value == i128::from(values[slot]),
None => same(&Value::TinyInt(values[slot]), &column.value_at(row)),
},
StoredData::SmallInt(values) => match column.signed_at(row) {
Some(value) => value == i128::from(values[slot]),
None => same(&Value::SmallInt(values[slot]), &column.value_at(row)),
},
StoredData::Integer(values) => match column.signed_at(row) {
Some(value) => value == i128::from(values[slot]),
None => same(&Value::Integer(values[slot]), &column.value_at(row)),
},
StoredData::BigInt(values) => match column.signed_at(row) {
Some(value) => value == i128::from(values[slot]),
None => same(&Value::BigInt(values[slot]), &column.value_at(row)),
},
StoredData::Wide { ty, values } => match column.signed_at(row) {
Some(value) => value == values[slot],
None => same(&one_integer_as(ty, values[slot]), &column.value_at(row)),
},
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::StableText { dictionary, codes } => {
match column.stable_dictionary_parts() {
Some((incoming, values)) if Arc::ptr_eq(dictionary, values) => {
incoming.get(row) == codes.get(slot)
}
_ => dictionary.bytes_at(codes[slot] as usize) == column.bytes_at(row),
}
}
StoredData::Other(values) => same(&values[slot].value(), &column.value_at(row)),
}
}
fn holds_run(&self, here: &[Step], seen: &[u64], column: &Vector, same: &mut [bool]) {
let validity = column.validity();
if let StoredData::StableText { dictionary, codes: stored } = &self.data
&& let Some((values, incoming)) = column.stable_dictionary_parts()
&& Arc::ptr_eq(dictionary, incoming)
{
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
*flag = match values.get(step.row) {
_ if !self.valid[slot] => !validity.is_valid(step.row),
Some(code) => validity.is_valid(step.row) && Some(code) == stored.get(slot),
None => false,
};
}
return;
}
if let Some(packed) = column.packed_parts() {
if self.packed_run(here, seen, same, &packed, |row| !validity.is_valid(row), |row| row)
{
return;
}
} else if let Some((at, values)) = column.positions() {
let inner = values.validity();
let nulled = |row: usize| {
!validity.is_valid(row)
|| at.get(row).is_none_or(|&code| !inner.is_valid(code as usize))
};
let code = |row: usize| at.get(row).map_or(usize::MAX, |&code| code as usize);
if let Some(packed) = values.packed_parts() {
if self.packed_run(here, seen, same, &packed, nulled, code) {
return;
}
} else if let Some(data) = values.data() {
if self.flat_run(here, seen, same, column, data, nulled, code) {
return;
}
}
}
if let Some(data) = column.data()
&& self.flat_run(
here,
seen,
same,
column,
data,
|row| !validity.is_valid(row),
|row| row,
)
{
return;
}
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if *flag {
*flag = self.holds(slot_of(bucket) as usize, column, step.row);
}
}
}
#[expect(
clippy::too_many_arguments,
reason = "the batch as three parallel runs, the column and the run inside it, and the two \
mappings that say where a row's value and a row's nulls are"
)]
fn flat_run<N: Fn(usize) -> bool, C: Fn(usize) -> usize>(
&self,
here: &[Step],
seen: &[u64],
same: &mut [bool],
column: &Vector,
data: &Data,
nulled: N,
code: C,
) -> bool {
macro_rules! run {
($stored:expr, $values:expr) => {{
let stored = $stored;
let values = $values.as_slice();
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
*flag = match values.get(code(step.row)) {
_ if !self.valid[slot] => nulled(step.row),
Some(value) => !nulled(step.row) && *value == stored[slot],
None => self.holds(slot, column, step.row),
};
}
return true;
}};
}
macro_rules! wide {
($stored:expr, $values:expr) => {{
let stored = $stored;
let values = $values.as_slice();
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
*flag = match values.get(code(step.row)) {
_ if !self.valid[slot] => nulled(step.row),
Some(value) => !nulled(step.row) && i128::from(*value) == stored[slot],
None => self.holds(slot, column, step.row),
};
}
return true;
}};
}
match (&self.data, data) {
(StoredData::TinyInt(stored), Data::Int8(values)) => run!(stored, values),
(StoredData::SmallInt(stored), Data::Int16(values)) => run!(stored, values),
(StoredData::Integer(stored), Data::Int32(values)) => run!(stored, values),
(StoredData::BigInt(stored), Data::Int64(values)) => run!(stored, values),
(StoredData::Wide { values: stored, .. }, Data::Int128(values)) => run!(stored, values),
(StoredData::Wide { values: stored, .. }, Data::Int8(values)) => wide!(stored, values),
(StoredData::Wide { values: stored, .. }, Data::Int16(values)) => wide!(stored, values),
(StoredData::Wide { values: stored, .. }, Data::Int32(values)) => wide!(stored, values),
(StoredData::Wide { values: stored, .. }, Data::Int64(values)) => wide!(stored, values),
(StoredData::Varchar(stored), Data::Varlen(strings)) => {
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
*flag = match strings.bytes(code(step.row)) {
_ if !self.valid[slot] => nulled(step.row),
Some(bytes) => !nulled(step.row) && bytes == stored.get(slot),
None => self.holds(slot, column, step.row),
};
}
true
}
_ => false,
}
}
fn packed_run(
&self,
here: &[Step],
seen: &[u64],
same: &mut [bool],
packed: &Packed<'_>,
nulled: impl Fn(usize) -> bool,
code: impl Fn(usize) -> usize,
) -> bool {
macro_rules! run {
($stored:expr) => {{
let stored = $stored;
let base = packed.base();
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
let missing = nulled(step.row);
*flag = if !self.valid[slot] {
missing
} else if missing {
false
} else {
base + i128::from(packed.code(code(step.row))) == i128::from(stored[slot])
};
}
return true;
}};
}
macro_rules! held {
($stored:expr) => {{
let stored = $stored;
let base = packed.base();
for ((step, &bucket), flag) in here.iter().zip(seen).zip(same.iter_mut()) {
if !*flag {
continue;
}
let slot = slot_of(bucket) as usize;
let missing = nulled(step.row);
*flag = if !self.valid[slot] {
missing
} else if missing {
false
} else {
base + i128::from(packed.code(code(step.row))) == stored[slot]
};
}
return true;
}};
}
match &self.data {
StoredData::TinyInt(stored) => run!(stored),
StoredData::SmallInt(stored) => run!(stored),
StoredData::Integer(stored) => run!(stored),
StoredData::BigInt(stored) => run!(stored),
StoredData::Wide { values, .. } => held!(values),
_ => false,
}
}
fn vector(
&self,
ty: &rudb_common::LogicalType,
range: std::ops::Range<usize>,
) -> Result<Vector> {
let (start, len) = (range.start, range.len());
let data = match &self.data {
StoredData::TinyInt(values) => Data::Int8(values[range.clone()].to_vec().into()),
StoredData::SmallInt(values) => Data::Int16(values[range.clone()].to_vec().into()),
StoredData::Integer(values) => Data::Int32(values[range.clone()].to_vec().into()),
StoredData::BigInt(values) => Data::Int64(values[range.clone()].to_vec().into()),
StoredData::Varchar(values) => {
let mut out = rudb_vector::StringColumn::with_capacity(len);
for slot in range.clone() {
if self.valid[slot] {
out.push_bytes(values.get(slot));
} else {
out.push("");
}
}
Data::Varlen(out)
}
StoredData::StableText { dictionary, codes } => {
let vector = Vector::stable_dictionary(
codes[range.clone()].to_vec(),
Arc::clone(dictionary),
)?;
let valid = &self.valid;
let validity = rudb_vector::Validity::from_iter(len, |index| valid[start + index]);
return Ok(vector.with_validity(validity));
}
StoredData::Wide { .. } | StoredData::Other(_) => {
return Vector::from_values(ty.clone(), &self.values(range));
}
};
let valid = &self.valid;
let validity = rudb_vector::Validity::from_iter(len, |index| valid[start + index]);
let vector = Vector::flat(ty.clone(), data)?.with_validity(validity);
if matches!(self.data, StoredData::Varchar(_)) { vector.shared_text() } else { Ok(vector) }
}
fn values(&self, range: std::ops::Range<usize>) -> Vec<Value> {
range
.map(|slot| {
if !self.valid[slot] {
return Value::Null;
}
match &self.data {
StoredData::TinyInt(values) => Value::TinyInt(values[slot]),
StoredData::SmallInt(values) => Value::SmallInt(values[slot]),
StoredData::Integer(values) => Value::Integer(values[slot]),
StoredData::BigInt(values) => Value::BigInt(values[slot]),
StoredData::Wide { ty, values } => one_integer_as(ty, values[slot]),
StoredData::Varchar(values) => Value::Varchar(values.string(slot)),
StoredData::StableText { dictionary, codes } => {
dictionary.value_at(codes[slot] as usize)
}
StoredData::Other(values) => values[slot].value(),
}
})
.collect()
}
fn vector_at(&self, ty: &rudb_common::LogicalType, slots: &[usize]) -> Result<Vector> {
if let StoredData::StableText { dictionary, codes } = &self.data {
let picked = slots.iter().map(|&slot| codes[slot]).collect();
let vector = Vector::stable_dictionary(picked, Arc::clone(dictionary))?;
let valid = &self.valid;
let validity =
rudb_vector::Validity::from_iter(slots.len(), |index| valid[slots[index]]);
return Ok(vector.with_validity(validity));
}
Vector::from_values(ty.clone(), &self.values_at(slots))
}
fn values_at(&self, slots: &[usize]) -> Vec<Value> {
slots
.iter()
.map(|&slot| {
if !self.valid[slot] {
return Value::Null;
}
match &self.data {
StoredData::TinyInt(values) => Value::TinyInt(values[slot]),
StoredData::SmallInt(values) => Value::SmallInt(values[slot]),
StoredData::Integer(values) => Value::Integer(values[slot]),
StoredData::BigInt(values) => Value::BigInt(values[slot]),
StoredData::Wide { ty, values } => one_integer_as(ty, values[slot]),
StoredData::Varchar(values) => Value::Varchar(values.string(slot)),
StoredData::StableText { dictionary, codes } => {
dictionary.value_at(codes[slot] as usize)
}
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<u32>,
}
impl StringColumn {
fn push(&mut self, value: &[u8]) -> Result<()> {
let length = self.bytes.len().checked_add(value.len()).ok_or_else(|| {
Error::out_of_memory("an aggregate partition's string keys are too large")
})?;
let end = u32::try_from(length).map_err(|_| {
Error::out_of_memory("one aggregate partition holds more than 4 GiB of string keys")
})?;
self.bytes.extend_from_slice(value);
self.ends.push(end);
Ok(())
}
fn get(&self, slot: usize) -> &[u8] {
let start = slot.checked_sub(1).map_or(0, |before| self.ends[before]) as usize;
&self.bytes[start..self.ends[slot] as usize]
}
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::<u32>()
}
}
#[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),
TimeTz(i64),
Timestamp(i64),
TimestampTz(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::TimeTz(v) => Self::TimeTz(v),
Value::Timestamp(v) => Self::Timestamp(v),
Value::TimestampTz(v) => Self::TimestampTz(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::TimeTz(v) => Value::TimeTz(*v),
Self::Timestamp(v) => Value::Timestamp(*v),
Self::TimestampTz(v) => Value::TimestampTz(*v),
Self::Interval { months, days, micros } => {
Value::Interval { months: *months, days: *days, micros: *micros }
}
Self::Other(v) => (**v).clone(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Across {
OneInput,
TwoInputs,
}
pub(crate) fn hash(keys: &[Vector], rows: usize, hashes: &mut Vec<u64>, across: Across) {
hashes.clear();
hashes.resize(rows, 0);
if let ([column], Across::OneInput) = (keys, across)
&& let Some((codes, _)) = column.stable_dictionary_parts()
{
let validity = column.validity();
if let (false, Some(codes)) = (validity.has_nulls(rows), codes.get(..rows)) {
for (state, &code) in hashes.iter_mut().zip(codes) {
*state = spread(mix(0, u64::from(code)));
}
return;
}
for (row, state) in hashes.iter_mut().enumerate() {
let word = if validity.is_valid(row) { u64::from(codes[row]) } else { NOTHING };
*state = spread(mix(0, word));
}
return;
}
let last = keys.len().saturating_sub(1);
for (at, column) in keys.iter().enumerate() {
fold(column, rows, hashes, across, at == last);
}
}
#[inline]
fn end(state: u64, finish: bool) -> u64 {
if finish { spread(state) } else { state }
}
pub(crate) fn repeats(keys: &[Vector], rows: usize, least: usize, same: &mut Vec<bool>) -> usize {
same.clear();
same.resize(rows, true);
if rows == 0 {
return 0;
}
same[0] = false;
let mut marked = rows - 1;
for column in keys {
let read = repeats_in(column, rows, same);
marked = same.iter().filter(|&&flag| flag).count();
if !read || marked < least {
same.clear();
same.resize(rows, false);
return 0;
}
}
marked
}
fn repeats_in(column: &Vector, rows: usize, same: &mut [bool]) -> bool {
let validity = column.validity();
let same = &mut same[..rows];
macro_rules! run {
($values:expr) => {{
let values = $values.as_slice();
if values.len() < rows {
return false;
}
narrow(same, validity, |row| values[row] == values[row - 1]);
return true;
}};
}
if let Some(packed) = column.packed_parts() {
narrow(same, validity, |row| packed.code(row) == packed.code(row - 1));
return true;
}
if let Some(data) = column.data() {
match data {
Data::Bool(values) => run!(values),
Data::Int8(values) => run!(values),
Data::Int16(values) => run!(values),
Data::Int32(values) => run!(values),
Data::Int64(values) => run!(values),
Data::Int128(values) => run!(values),
Data::UInt8(values) => run!(values),
Data::UInt16(values) => run!(values),
Data::UInt32(values) => run!(values),
Data::UInt64(values) => run!(values),
Data::UInt128(values) => run!(values),
Data::Varlen(strings) => {
narrow(same, validity, |row| strings.bytes(row) == strings.bytes(row - 1));
return true;
}
_ => return false,
}
}
if let Some((at, _)) = column.positions() {
if at.len() < rows {
return false;
}
narrow(same, validity, |row| at[row] == at[row - 1]);
return true;
}
false
}
fn narrow(same: &mut [bool], validity: &rudb_vector::Validity, equal: impl Fn(usize) -> bool) {
for (row, flag) in same.iter_mut().enumerate().skip(1) {
*flag = *flag
&& validity.is_valid(row) == validity.is_valid(row - 1)
&& (!validity.is_valid(row) || equal(row));
}
}
fn fold(column: &Vector, rows: usize, hashes: &mut [u64], across: Across, finish: bool) {
let validity = column.validity();
if across == Across::OneInput
&& let Some((codes, _)) = column.stable_dictionary_parts()
{
if let (false, Some(codes), Some(hashes)) =
(validity.has_nulls(rows), codes.get(..rows), hashes.get_mut(..rows))
{
for (state, &code) in hashes.iter_mut().zip(codes) {
*state = end(mix(*state, u64::from(code)), finish);
}
return;
}
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let one = if validity.is_valid(row) { u64::from(codes[row]) } else { NOTHING };
*state = end(mix(*state, one), finish);
}
return;
}
let wide = match column.logical_type() {
rudb_common::LogicalType::HugeInt | rudb_common::LogicalType::UHugeInt => true,
rudb_common::LogicalType::Decimal { width, .. } => wide_decimal(*width),
_ => false,
};
let straight = !validity.has_nulls(rows);
if let Some(packed) = column.packed_parts() {
fold_packed(&packed, wide, rows, straight.then_some(Reads::Own), finish, hashes, |row| {
validity.is_valid(row).then_some(row)
});
return;
}
if let Some(data) = column.data()
&& fold_data(data, rows, hashes, straight.then_some(Reads::Own), finish, |row| {
validity.is_valid(row).then_some(row)
})
{
return;
}
if let Some((at, values)) = column.positions() {
let inner = values.validity();
let clean = !validity.has_nulls(rows)
&& values.len() <= rows.saturating_mul(4)
&& !inner.has_nulls(values.len());
let through = clean.then_some(Reads::Codes(&at[..]));
if let Some(data) = values.data() {
let pick = |row: usize| {
if !validity.is_valid(row) {
return None;
}
let code = *at.get(row)? as usize;
inner.is_valid(code).then_some(code)
};
if fold_data(data, rows, hashes, through, finish, pick) {
return;
}
}
if let Some(packed) = values.packed_parts() {
fold_packed(&packed, wide, rows, through, finish, hashes, |row| {
if !validity.is_valid(row) {
return None;
}
let code = *at.get(row)? as usize;
inner.is_valid(code).then_some(code)
});
return;
}
}
let text = column.logical_type() == &rudb_common::LogicalType::Varchar;
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let one = if text {
match column.bytes_at(row) {
Some(bytes) => mix(*state, bytes_word(bytes)),
None => mix(*state, NOTHING),
}
} else {
fold_value(*state, &column.value_at(row))
};
*state = end(one, finish);
}
}
#[derive(Clone, Copy)]
enum Reads<'a> {
Own,
Codes(&'a [u32]),
}
fn fold_packed(
packed: &Packed<'_>,
wide: bool,
rows: usize,
straight: Option<Reads<'_>>,
finish: bool,
hashes: &mut [u64],
pick: impl Fn(usize) -> Option<usize>,
) {
let base = packed.base();
match straight {
Some(Reads::Own) => {
let rows = rows.min(hashes.len());
straight_packed(packed, wide, finish, &mut hashes[..rows], 0..rows);
return;
}
Some(Reads::Codes(codes)) => {
if let Some(codes) = codes.get(..rows) {
let at = codes.iter().map(|&code| code as usize);
let rows = rows.min(hashes.len());
straight_packed(packed, wide, finish, &mut hashes[..rows], at);
return;
}
}
None => {}
}
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let Some(code) = pick(row) else {
*state = end(mix(*state, NOTHING), finish);
continue;
};
let value = base + i128::from(packed.code(code));
let one = if wide {
mix(mix(*state, value as u64), (value >> 64) as u64)
} else {
mix(*state, value as u64)
};
*state = end(one, finish);
}
}
fn straight_packed(
packed: &Packed<'_>,
wide: bool,
finish: bool,
hashes: &mut [u64],
at: impl Iterator<Item = usize>,
) {
fn run<const WIDE: bool, const FINISH: bool>(
packed: &Packed<'_>,
hashes: &mut [u64],
at: impl Iterator<Item = usize>,
) {
let base = packed.base();
for (state, code) in hashes.iter_mut().zip(at) {
let value = base + i128::from(packed.code(code));
let one = if WIDE {
mix(mix(*state, value as u64), (value >> 64) as u64)
} else {
mix(*state, value as u64)
};
*state = end(one, FINISH);
}
}
match (wide, finish) {
(false, false) => run::<false, false>(packed, hashes, at),
(false, true) => run::<false, true>(packed, hashes, at),
(true, false) => run::<true, false>(packed, hashes, at),
(true, true) => run::<true, true>(packed, hashes, at),
}
}
fn fold_data(
data: &Data,
rows: usize,
hashes: &mut [u64],
straight: Option<Reads<'_>>,
finish: bool,
pick: impl Fn(usize) -> Option<usize>,
) -> bool {
macro_rules! run {
($values:expr, $word:expr) => {{
let values = $values.as_slice();
let word = $word;
match straight {
Some(Reads::Own) => {
if let (Some(values), Some(hashes)) =
(values.get(..rows), hashes.get_mut(..rows))
{
for (state, value) in hashes.iter_mut().zip(values) {
*state = end(mix(*state, word(*value)), finish);
}
return true;
}
}
Some(Reads::Codes(codes)) => {
if let Some(codes) = codes.get(..rows) {
for (state, &code) in hashes.iter_mut().zip(codes) {
let one = match values.get(code as usize) {
Some(value) => word(*value),
None => NOTHING,
};
*state = end(mix(*state, one), finish);
}
return true;
}
}
None => {}
}
for (row, state) in hashes.iter_mut().enumerate().take(rows) {
let one = match pick(row).and_then(|at| values.get(at)) {
Some(value) => word(*value),
None => NOTHING,
};
*state = end(mix(*state, one), finish);
}
return true;
}};
}
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 at = match straight {
Some(Reads::Own) => Some(row),
Some(Reads::Codes(codes)) => codes.get(row).map(|&code| code as usize),
None => pick(row),
};
let one = match at.and_then(|at| strings.bytes(at)) {
Some(bytes) => bytes_word(bytes),
None => NOTHING,
};
*state = end(mix(*state, one), finish);
}
true
}
_ => false,
}
}
fn wide_decimal(width: u8) -> bool {
matches!(
rudb_common::LogicalType::Decimal { width, scale: 0 }.physical(),
rudb_common::PhysicalType::Int128
)
}
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::TimeTz(x)
| Value::Timestamp(x)
| Value::TimestampTz(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) => mix(mix(state, *x as u64), (*x >> 64) as u64),
Value::Decimal { unscaled: x, width, .. } => match wide_decimal(*width) {
true => mix(mix(state, *x as u64), (*x >> 64) as u64),
false => mix(state, *x 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>());
}
#[test]
fn runs_are_cut_where_the_value_changes_and_refused_past_the_limit() {
let values: Vec<i64> = [5; 15].into_iter().chain([7; 20]).chain([5, 9]).collect();
let mut runs = Vec::new();
assert!(runs_in(&values, 8, &mut runs, |value| value as usize * 2));
assert_eq!(runs, vec![(10, 15), (14, 35), (10, 36), (18, 37)]);
assert!(!runs_in(&values, 3, &mut runs, |value| value as usize));
assert!(runs.is_empty());
assert!(runs_in(&[3u32; 40], 0, &mut runs, |code| code as usize));
assert_eq!(runs, vec![(3, 40)]);
assert!(!runs_in::<u32>(&[], 8, &mut runs, |code| code as usize));
}
fn hashed(column: &Vector) -> Vec<u64> {
let mut hashes = Vec::new();
hash(std::slice::from_ref(column), column.len(), &mut hashes, Across::OneInput);
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));
}
fn one_at_a_time(keys: &[Vector], rows: usize, types: &[LogicalType]) -> (Table, Vec<usize>) {
let mut table = Table::new(types);
let mut hashes = Vec::new();
hash(keys, rows, &mut hashes, Across::OneInput);
let mut slots = Vec::new();
for (row, &hash) in hashes.iter().enumerate() {
slots.push(match table.probe(hash, keys, row) {
Probe::Found(slot) => slot,
Probe::Vacant(bucket) => {
table.insert(bucket, hash, keys, row).expect("room for this group")
}
});
}
(table, slots)
}
fn a_batch_at_a_time(
keys: &[Vector],
rows: usize,
types: &[LogicalType],
) -> (Table, Vec<usize>) {
let mut table = Table::new(types);
let mut hashes = Vec::new();
hash(keys, rows, &mut hashes, Across::OneInput);
let mut slots = vec![usize::MAX; rows];
let mut walk = Walk::default();
let mut from = 0;
while from < rows {
let upto = (from + BATCH).min(rows);
table.probe_run(&hashes, keys, from, upto, &mut slots, &mut walk);
from = upto;
for &row in walk.pending() {
slots[row] = match table.probe(hashes[row], keys, row) {
Probe::Found(slot) => slot,
Probe::Vacant(bucket) => {
table.insert(bucket, hashes[row], keys, row).expect("room for this group")
}
};
}
}
(table, slots)
}
#[test]
fn a_batch_at_a_time_finds_the_groups_one_at_a_time_found_in_the_order_it_found_them() {
let values: Vec<Value> = (0..40_000)
.map(|row: i64| match row % 97 {
0 => Value::Null,
_ => Value::BigInt((row * 7919) % 12_007),
})
.collect();
let keys = [flat(LogicalType::BigInt, &values)];
let types = [LogicalType::BigInt];
let (was, before) = one_at_a_time(&keys, values.len(), &types);
let (now, after) = a_batch_at_a_time(&keys, values.len(), &types);
assert_eq!(before, after);
assert_eq!(was.len(), now.len());
assert!(now.buckets.len() > HOT, "the test has to reach the batched path");
}
fn over_a_range(
keys: &[Vector],
rows: usize,
types: &[LogicalType],
low: i128,
values: u64,
) -> (Table, Vec<usize>) {
let mut table = Table::new(types).over_range(low, values, &types[0]);
let mut hashes = Vec::new();
hash(keys, rows, &mut hashes, Across::OneInput);
let mut slots = vec![usize::MAX; rows];
let mut walk = Walk::default();
let mut from = 0;
while from < rows {
let upto = (from + BATCH).min(rows);
table.probe_run(&hashes, keys, from, upto, &mut slots, &mut walk);
from = upto;
for &row in walk.pending() {
slots[row] = match table.probe(hashes[row], keys, row) {
Probe::Found(slot) => slot,
Probe::Vacant(bucket) => {
table.insert(bucket, hashes[row], keys, row).expect("room for this group")
}
};
}
}
(table, slots)
}
#[test]
fn a_direct_index_finds_the_same_groups_in_the_same_slots() {
let values: Vec<Value> = (0..40_000)
.map(|row: i64| match row % 97 {
0 => Value::Null,
_ => Value::BigInt((row * 7919) % 12_007),
})
.collect();
let keys = [flat(LogicalType::BigInt, &values)];
let types = [LogicalType::BigInt];
let (was, before) = a_batch_at_a_time(&keys, values.len(), &types);
let (now, after) = over_a_range(&keys, values.len(), &types, 0, 12_007);
assert!(now.direct.is_some(), "the test has to reach the direct path");
assert_eq!(before, after);
assert_eq!(was.len(), now.len());
}
#[test]
fn a_range_below_zero_addresses_the_same_way() {
let values: Vec<Value> =
(0..4_000).map(|row: i32| Value::Integer((row % 601) - 300)).collect();
let keys = [flat(LogicalType::Integer, &values)];
let types = [LogicalType::Integer];
let (_, before) = a_batch_at_a_time(&keys, values.len(), &types);
let (now, after) = over_a_range(&keys, values.len(), &types, -300, 601);
assert!(now.direct.is_some());
assert_eq!(before, after);
assert_eq!(now.len(), 601);
}
#[test]
fn a_value_outside_the_range_is_answered_by_the_buckets() {
let values: Vec<Value> = (0..2_000).map(|row: i64| Value::BigInt(row % 500)).collect();
let keys = [flat(LogicalType::BigInt, &values)];
let types = [LogicalType::BigInt];
let (_, before) = a_batch_at_a_time(&keys, values.len(), &types);
let (now, after) = over_a_range(&keys, values.len(), &types, 0, 10);
assert_eq!(before, after);
assert_eq!(now.len(), 500);
}
#[test]
fn a_dictionary_key_is_answered_by_the_buckets() {
let seen = [Value::Integer(3), Value::Integer(7), Value::Null];
let values: Vec<Value> =
(0..300).map(|row: usize| seen[row % seen.len()].clone()).collect();
let codes: Vec<u32> = (0..300).map(|row| (row % seen.len()) as u32).collect();
let keys = [dictionary_of(LogicalType::Integer, &seen, codes, &values)];
let types = [LogicalType::Integer];
let plain = [flat(LogicalType::Integer, &values)];
let (_, before) = a_batch_at_a_time(&plain, values.len(), &types);
let (now, after) = over_a_range(&keys, values.len(), &types, 0, 16);
assert!(now.direct.is_some(), "the index is built and simply not read from");
assert_eq!(before, after);
assert_eq!(now.len(), 3);
}
#[test]
fn a_type_the_two_sides_read_differently_gets_no_index() {
let scaled = LogicalType::Decimal { width: 18, scale: 2 };
assert!(
Table::new(std::slice::from_ref(&scaled)).over_range(0, 100, &scaled).direct.is_none()
);
assert!(
Table::new(&[LogicalType::Timestamp])
.over_range(0, 100, &LogicalType::Timestamp)
.direct
.is_none()
);
assert!(
Table::new(&[LogicalType::Date])
.over_range(0, 100, &LogicalType::Date)
.direct
.is_some()
);
}
#[test]
fn a_key_of_two_columns_gets_no_index() {
let types = [LogicalType::Integer, LogicalType::Integer];
assert!(Table::new(&types).over_range(0, 100, &types[0]).direct.is_none());
}
fn dictionary_of(ty: LogicalType, seen: &[Value], codes: Vec<u32>, values: &[Value]) -> Vector {
let valid =
rudb_vector::Validity::from_iter(values.len(), |row| values[row] != Value::Null);
Vector::dictionary(codes, flat(ty, seen))
.expect("a dictionary of those values")
.with_validity(valid)
}
#[test]
fn the_batched_key_compare_agrees_with_the_one_at_a_time_one_on_every_form() {
let rows = 30_000;
let number = |row: i64| (row * 7919) % 5003;
let word = |row: i64| (row * 104_729) % 4001;
let numbers: Vec<Value> = (0..rows)
.map(|row| match row % 61 {
0 => Value::Null,
_ => Value::Integer(number(row) as i32),
})
.collect();
let words: Vec<Value> = (0..rows)
.map(|row| match row % 37 {
0 => Value::Null,
_ => Value::Varchar(format!("row {}", word(row))),
})
.collect();
let types = [LogicalType::Integer, LogicalType::Varchar];
let flatly = [flat(LogicalType::Integer, &numbers), flat(LogicalType::Varchar, &words)];
let (was, before) = one_at_a_time(&flatly, numbers.len(), &types);
let (now, after) = a_batch_at_a_time(&flatly, numbers.len(), &types);
assert_eq!(before, after);
assert_eq!(was.len(), now.len());
assert!(now.buckets.len() > HOT, "the test has to reach the batched path");
let digits: Vec<Value> = (0..5003).map(Value::Integer).collect();
let phrases: Vec<Value> = (0..4001).map(|at| Value::Varchar(format!("row {at}"))).collect();
let indirect = [
dictionary_of(
LogicalType::Integer,
&digits,
(0..rows).map(|row| number(row) as u32).collect(),
&numbers,
),
dictionary_of(
LogicalType::Varchar,
&phrases,
(0..rows).map(|row| word(row) as u32).collect(),
&words,
),
];
let (_, through) = a_batch_at_a_time(&indirect, numbers.len(), &types);
assert_eq!(before, through);
}
#[test]
fn a_packed_run_and_a_dictionary_over_one_group_as_the_flat_column_they_stand_for() {
let rows = 30_000i64;
let number = |row: i64| (row * 7919) % 5003;
let values: Vec<Value> = (0..rows)
.map(|row| match row % 61 {
0 => Value::Null,
_ => Value::BigInt(number(row) + 1_000_000),
})
.collect();
let types = [LogicalType::BigInt];
let flatly = [flat(LogicalType::BigInt, &values)];
let (was, before) = one_at_a_time(&flatly, values.len(), &types);
assert!(was.buckets.len() > HOT, "the test has to reach the batched path");
let packed = [flatly[0].bit_packed().expect("a packed run of those values")];
assert_eq!(
packed[0].form(),
rudb_vector::Form::BitPacked,
"the test needs the packed form"
);
assert_eq!(hashed(&flatly[0]), hashed(&packed[0]));
let (_, through_packed) = a_batch_at_a_time(&packed, values.len(), &types);
assert_eq!(before, through_packed);
let distinct: Vec<Value> = (0..5003).map(|at| Value::BigInt(at + 1_000_000)).collect();
let held = flat(LogicalType::BigInt, &distinct)
.bit_packed()
.expect("a packed run of the distinct values");
let valid =
rudb_vector::Validity::from_iter(values.len(), |row| values[row] != Value::Null);
let coded = [Vector::dictionary((0..rows).map(|row| number(row) as u32).collect(), held)
.expect("a dictionary over that packed run")
.with_validity(valid)];
assert_eq!(hashed(&flatly[0]), hashed(&coded[0]));
let (_, through_coded) = a_batch_at_a_time(&coded, values.len(), &types);
assert_eq!(before, through_coded);
}
#[test]
fn a_dictionary_whose_nulls_are_in_its_values_groups_as_the_flat_column_it_stands_for() {
let rows = 30_000i64;
let number = |row: i64| (row * 7919) % 5003;
let word = |row: i64| (row * 104_729) % 4001;
let digit =
|code: i64| if code % 71 == 0 { Value::Null } else { Value::BigInt(code + 1_000_000) };
let phrase = |code: i64| {
if code % 53 == 0 { Value::Null } else { Value::Varchar(format!("row {code}")) }
};
let numbers: Vec<Value> = (0..rows).map(|row| digit(number(row))).collect();
let words: Vec<Value> = (0..rows).map(|row| phrase(word(row))).collect();
let types = [LogicalType::BigInt, LogicalType::Varchar];
let flatly = [flat(LogicalType::BigInt, &numbers), flat(LogicalType::Varchar, &words)];
let (was, before) = one_at_a_time(&flatly, numbers.len(), &types);
assert!(was.buckets.len() > HOT, "the test has to reach the batched path");
let digits: Vec<Value> = (0..5003).map(digit).collect();
let phrases: Vec<Value> = (0..4001).map(phrase).collect();
let coded = [
Vector::dictionary(
(0..rows).map(|row| number(row) as u32).collect(),
flat(LogicalType::BigInt, &digits),
)
.expect("a dictionary over those numbers"),
Vector::dictionary(
(0..rows).map(|row| word(row) as u32).collect(),
flat(LogicalType::Varchar, &phrases),
)
.expect("a dictionary over those words"),
];
assert_eq!(hashed(&flatly[0]), hashed(&coded[0]));
assert_eq!(hashed(&flatly[1]), hashed(&coded[1]));
let (_, through) = a_batch_at_a_time(&coded, numbers.len(), &types);
assert_eq!(before, through);
}
#[test]
fn a_decimal_narrower_than_a_hugeint_hashes_the_same_in_every_form() {
for width in [4u8, 9, 18, 30] {
let ty = LogicalType::Decimal { width, scale: 2 };
let of = |unscaled: i128| Value::Decimal { unscaled, width, scale: 2 };
let values: Vec<Value> = (0..600).map(|row| of(row % 100)).collect();
let plain = flat(ty.clone(), &values);
let packed = plain.bit_packed().expect("a packed run of those values");
assert_eq!(packed.form(), rudb_vector::Form::BitPacked, "DECIMAL({width}) has to pack");
assert_eq!(hashed(&plain), hashed(&packed), "DECIMAL({width}) packed against flat");
let one = Vector::constant(ty.clone(), of(7), 4);
let same = flat(ty.clone(), &vec![of(7); 4]);
assert_eq!(hashed(&one), hashed(&same), "DECIMAL({width}) constant against flat");
}
}
fn a_key_of_every_form(ty: &LogicalType, of: impl Fn(i64) -> Value) {
let rows = 30_000i64;
let number = |row: i64| (row * 7919) % 5003;
let values: Vec<Value> = (0..rows)
.map(|row| if row % 61 == 0 { Value::Null } else { of(number(row)) })
.collect();
let types = [ty.clone()];
let flatly = [flat(ty.clone(), &values)];
let (was, before) = one_at_a_time(&flatly, values.len(), &types);
assert!(was.buckets.len() > HOT, "{ty} has to reach the batched path");
let (now, after) = a_batch_at_a_time(&flatly, values.len(), &types);
assert_eq!(before, after, "{ty} flat");
assert_eq!(was.len(), now.len(), "{ty} flat");
let mut want = vec![Value::Null; now.len()];
let mut filled = vec![false; now.len()];
for (row, &slot) in after.iter().enumerate() {
if !std::mem::replace(&mut filled[slot], true) {
want[slot] = values[row].clone();
}
}
let out = now.column(0, ty, 0..now.len()).expect("the keys of every group");
let got: Vec<Value> = (0..out.len()).map(|slot| out.value_at(slot)).collect();
assert_eq!(want, got, "{ty} keys on the way out");
let packed = [flatly[0].bit_packed().expect("a packed run of those values")];
assert_eq!(packed[0].form(), rudb_vector::Form::BitPacked, "{ty} has to pack");
assert_eq!(hashed(&flatly[0]), hashed(&packed[0]), "{ty} packed");
let (_, through_packed) = a_batch_at_a_time(&packed, values.len(), &types);
assert_eq!(before, through_packed, "{ty} packed");
let distinct: Vec<Value> = (0..5003).map(&of).collect();
let held =
flat(ty.clone(), &distinct).bit_packed().expect("a packed run of the distinct values");
let valid =
rudb_vector::Validity::from_iter(values.len(), |row| values[row] != Value::Null);
let coded = [Vector::dictionary((0..rows).map(|row| number(row) as u32).collect(), held)
.expect("a dictionary over that packed run")
.with_validity(valid)];
assert_eq!(hashed(&flatly[0]), hashed(&coded[0]), "{ty} coded");
let (_, through_coded) = a_batch_at_a_time(&coded, values.len(), &types);
assert_eq!(before, through_coded, "{ty} coded");
}
#[test]
fn a_key_that_is_one_integer_without_being_an_integer_groups_the_same_in_every_form() {
a_key_of_every_form(&LogicalType::Date, |code| Value::Date(code as i32));
a_key_of_every_form(&LogicalType::Time, |code| Value::Time(code * 1_000));
a_key_of_every_form(&LogicalType::Timestamp, |code| Value::Timestamp(code * 1_000));
a_key_of_every_form(&LogicalType::HugeInt, |code| Value::HugeInt(i128::from(code)));
a_key_of_every_form(&LogicalType::Decimal { width: 9, scale: 2 }, |code| Value::Decimal {
unscaled: i128::from(code),
width: 9,
scale: 2,
});
a_key_of_every_form(&LogicalType::Decimal { width: 30, scale: 2 }, |code| Value::Decimal {
unscaled: i128::from(code),
width: 30,
scale: 2,
});
}
#[test]
fn rows_of_one_new_group_in_one_batch_get_one_slot() {
let values = vec![Value::Integer(4); BATCH * 3];
let keys = [flat(LogicalType::Integer, &values)];
let types = [LogicalType::Integer];
let (table, slots) = a_batch_at_a_time(&keys, values.len(), &types);
assert_eq!(table.len(), 1);
assert!(slots.iter().all(|&slot| slot == 0));
}
#[test]
fn the_rows_a_filter_kept_hash_the_way_they_hashed_before_it() {
let rows = 3_000usize;
let kept: Vec<u32> = (0..rows as u32).filter(|row| row % 3 != 1).collect();
for ty in [LogicalType::Integer, LogicalType::BigInt, LogicalType::Varchar] {
let of = |row: usize| match &ty {
LogicalType::Integer => Value::Integer(((row * 7919) % 5003) as i32),
LogicalType::BigInt => Value::BigInt(1_000_000 + ((row * 7919) % 5003) as i64),
_ => Value::Varchar(format!("k{}", (row * 7919) % 5003)),
};
let mut values: Vec<Value> = (0..rows).map(of).collect();
let whole = hashed(&flat(ty.clone(), &values));
let want: Vec<u64> = kept.iter().map(|&row| whole[row as usize]).collect();
let mut payloads = vec![flat(ty.clone(), &values)];
if ty != LogicalType::Varchar {
let packed = flat(ty.clone(), &values).bit_packed().expect("a packed column");
assert_eq!(packed.form(), rudb_vector::Form::BitPacked, "{ty} has to pack");
payloads.push(packed);
}
values[1] = Value::Null;
payloads.push(flat(ty.clone(), &values));
for payload in payloads {
let form = payload.form();
let selected = Vector::dictionary(kept.clone(), payload).expect("codes into it");
assert_eq!(want, hashed(&selected), "{ty} kept out of {form:?}");
}
}
}
#[test]
fn a_column_with_no_nulls_hashes_the_way_the_general_pass_hashes_it() {
let rows = 3_000usize;
for ty in [
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::Varchar,
LogicalType::Date,
LogicalType::Decimal { width: 9, scale: 2 },
] {
let of = |row: usize| match &ty {
LogicalType::Integer => Value::Integer(((row * 7919) % 5003) as i32),
LogicalType::BigInt => Value::BigInt(((row * 7919) % 5003) as i64),
LogicalType::Varchar => Value::Varchar(format!("k{}", (row * 7919) % 5003)),
LogicalType::Date => Value::Date(((row * 7919) % 5003) as i32),
_ => Value::Decimal { unscaled: ((row * 7919) % 5003) as i128, width: 9, scale: 2 },
};
let values: Vec<Value> = (0..rows).map(of).collect();
let plain = flat(ty.clone(), &values);
assert!(!plain.validity().has_nulls(rows), "{ty} has to have no nulls");
let want = hashed(&plain);
let coded = Vector::dictionary((0..rows as u32).collect(), flat(ty.clone(), &values))
.expect("a dictionary over those values");
assert_eq!(want, hashed(&coded), "{ty} through codes");
let mut with_a_null = values.clone();
with_a_null.push(Value::Null);
let mixed = flat(ty.clone(), &with_a_null);
assert!(mixed.validity().has_nulls(rows + 1), "{ty} has to have a null");
assert_eq!(want, hashed(&mixed)[..rows], "{ty} beside a null");
}
}
#[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, Across::OneInput);
hash(&[twos, ones], 1, &mut backwards, Across::OneInput);
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, Across::OneInput);
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 two_null_rows_are_one_group_when_they_arrive_behind_a_dictionary() {
let values = flat(LogicalType::Integer, &[Value::Null, Value::Integer(1)]);
let keys = [Vector::dictionary(vec![0, 0, 1], values).expect("a dictionary of those rows")];
let (table, slots) = one_at_a_time(&keys, 3, &[LogicalType::Integer]);
assert_eq!(slots, [0, 0, 1], "the two nulls did not find each other");
assert_eq!(table.len(), 2);
}
#[test]
fn a_null_group_does_not_take_a_row_that_has_a_value_behind_a_dictionary() {
let values = flat(LogicalType::Varchar, &[Value::Null, Value::Varchar("ada".into())]);
let keys = [Vector::dictionary(vec![0, 1, 0], values).expect("a dictionary of those rows")];
let (table, slots) = one_at_a_time(&keys, 3, &[LogicalType::Varchar]);
assert_eq!(slots, [0, 1, 0]);
assert_eq!(table.len(), 2);
}
#[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, Across::OneInput);
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, Across::OneInput);
hash(std::slice::from_ref(&grace), 1, &mut second, Across::OneInput);
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 a_salt_of_all_ones_does_not_read_as_an_empty_bucket() {
for slot in [0usize, 1, 63, 64, 65_535, LIMIT - 1] {
for salt in [0u32, 1, u32::MAX - 1, u32::MAX] {
let bucket = bucket_of(salt, slot);
assert_eq!(slot_of(bucket) as usize, slot, "slot {slot} under salt {salt}");
assert_eq!(bucket_salt(bucket), salt, "salt {salt} over slot {slot}");
assert_ne!(slot_of(bucket), EMPTY, "slot {slot} read as an empty bucket");
}
}
assert_eq!(slot_of(VACANT), EMPTY);
}
#[test]
fn the_salt_is_bits_the_bucket_number_does_not_cover() {
let low = 0x0000_0000_dead_beef;
let high = 0xffff_ffff_dead_beef;
assert_eq!(low as u32, high as u32, "the test wants two hashes that share a bucket");
assert_ne!(salt_of(low), salt_of(high));
}
#[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, Across::OneInput);
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, &LogicalType::BigInt, 0..values.len()).expect("a bigint key");
assert_eq!(column.len(), values.len());
assert_eq!(column.value_at(7), Value::BigInt(7));
}
#[test]
fn a_packed_integer_column_groups_the_same_as_the_flat_one_it_stands_for() {
let values: Vec<Value> = (0..256).map(|row| Value::BigInt(row % 7)).collect();
let plain = flat(LogicalType::BigInt, &values);
let packed = plain.bit_packed().expect("a column of seven small values packs");
assert_eq!(packed.signed_at(0), Some(0), "a packed row stays in code space");
assert_eq!(hashed(&packed), hashed(&plain), "packing does not change a key's hash");
let mut grouped = Vec::new();
for column in [&plain, &packed] {
let keys = std::slice::from_ref(column);
let hashes = hashed(column);
let mut table = Table::new(&[LogicalType::BigInt]);
let mut slots = Vec::new();
for (row, &one) in hashes.iter().enumerate() {
slots.push(match table.probe(one, keys, row) {
Probe::Found(slot) => slot,
Probe::Vacant(bucket) => table.insert(bucket, one, keys, row).expect("room"),
});
}
assert_eq!(table.len(), 7, "seven distinct keys whichever form they arrived in");
grouped.push(slots);
}
assert_eq!(grouped[0], grouped[1]);
}
#[test]
fn narrow_integer_keys_group_by_value_and_come_back_in_their_own_width() {
for (ty, modulus) in [(LogicalType::TinyInt, 251i64), (LogicalType::SmallInt, 12_007i64)] {
let tiny = ty == LogicalType::TinyInt;
let held = |row: i64| {
let value = (row * 7919) % modulus - modulus / 2;
if tiny { Value::TinyInt(value as i8) } else { Value::SmallInt(value as i16) }
};
let values: Vec<Value> = (0..40_000)
.map(|row: i64| if row % 53 == 0 { Value::Null } else { held(row) })
.collect();
let keys = [flat(ty.clone(), &values)];
let types = [ty.clone()];
let (was, before) = one_at_a_time(&keys, values.len(), &types);
let (now, after) = a_batch_at_a_time(&keys, values.len(), &types);
assert_eq!(
before, after,
"{ty:?} was batched into groups it did not make one at a time"
);
assert_eq!(was.len(), now.len());
assert!(tiny || now.buckets.len() > HOT, "the test has to reach the batched path");
let bytes = now.columns[0].footprint();
assert!(
bytes < now.len() * size_of::<Stored>(),
"{bytes} bytes held {} keys",
now.len()
);
let column = now.column(0, &ty, 0..now.len()).expect("a narrow key column");
for (row, &slot) in before.iter().enumerate() {
assert_eq!(column.value_at(slot), values[row], "row {row} of {ty:?}");
}
}
}
#[test]
fn a_key_column_comes_back_as_a_vector_with_its_nulls_where_they_were() {
let values = [Value::BigInt(5), Value::Null, Value::BigInt(9)];
let keys = [flat(LogicalType::BigInt, &values)];
let hashes = hashed(&keys[0]);
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");
};
table.insert(bucket, one, &keys, row).expect("room");
}
let whole = table.column(0, &LogicalType::BigInt, 0..3).expect("a bigint key");
assert_eq!(whole.value_at(0), Value::BigInt(5));
assert_eq!(whole.value_at(1), Value::Null);
assert_eq!(whole.value_at(2), Value::BigInt(9));
let tail = table.column(0, &LogicalType::BigInt, 1..3).expect("a bigint key");
assert_eq!(tail.len(), 2);
assert_eq!(tail.value_at(0), Value::Null);
assert_eq!(tail.value_at(1), Value::BigInt(9));
}
#[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, Across::OneInput);
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, Across::OneInput);
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()
);
}
fn coded_letters(codes: Vec<u32>, letters: &[&str]) -> Vector {
let values: Vec<Value> = letters.iter().map(|&text| Value::Varchar(text.into())).collect();
Vector::dictionary(codes, flat(LogicalType::Varchar, &values))
.expect("a dictionary of those letters")
}
fn placed(coded: &Coded<'_>, rows: usize) -> Vec<usize> {
let mut places = Vec::new();
coded.places(rows, &mut places);
places
}
#[test]
fn two_small_dictionaries_give_every_row_the_place_its_codes_say() {
let flags = coded_letters(vec![0, 1, 2, 0], &["A", "N", "R"]);
let status = coded_letters(vec![0, 1, 1, 0], &["F", "O"]);
let keys = [flags, status];
let coded = coded(&keys, 4).expect("two small dictionaries are read as codes");
assert_eq!(coded.combos(), 12);
assert_eq!(placed(&coded, 4), [0, 1 + 4, 2 + 4, 0]);
}
#[test]
fn a_null_row_takes_the_place_past_the_last_code() {
let inner = flat(LogicalType::Varchar, &[Value::Varchar("A".into()), Value::Null]);
let column = Vector::dictionary(vec![0, 1, 0], inner)
.expect("a dictionary whose second value is nothing")
.with_validity(rudb_vector::Validity::Mask({
let mut mask = rudb_vector::Bitmap::all_valid(3);
mask.set(2, false);
mask
}));
let keys = [column];
let coded = coded(&keys, 3).expect("one small dictionary is read as codes");
assert_eq!(coded.combos(), 3);
assert_eq!(placed(&coded, 3), [0, 2, 2]);
}
#[test]
fn a_flat_column_is_not_read_as_codes() {
let keys = [flat(LogicalType::Integer, &[Value::Integer(1), Value::Integer(2)])];
assert!(coded(&keys, 2).is_none());
}
fn packed_numbers(values: &[i64], width: u32, base: i128) -> Vector {
let bits = width as usize;
let mut words = vec![0_u64; (values.len() * bits).div_ceil(u64::BITS as usize) + 1];
for (row, &value) in values.iter().enumerate() {
let code =
u64::try_from(i128::from(value) - base).expect("a value at or above the base");
let at = row * bits;
let (word, offset) = (at / 64, at % 64);
words[word] |= code << offset;
if offset + bits > 64 {
words[word + 1] |= code >> (64 - offset);
}
}
Vector::packed(LogicalType::Integer, words, width, base, values.len())
.expect("a packed column of those values")
}
#[test]
fn a_packed_column_is_read_as_codes_at_its_own_width() {
let keys = [packed_numbers(&[1, 2, 7, 1], 3, 1)];
let coded = coded(&keys, 4).expect("a narrow packed column is read as codes");
assert_eq!(coded.combos(), 9);
assert_eq!(placed(&coded, 4), [0, 1, 6, 0]);
}
#[test]
fn a_packed_column_and_a_dictionary_share_one_map() {
let keys = [packed_numbers(&[1, 2, 1], 2, 1), coded_letters(vec![0, 1, 0], &["A", "N"])];
let coded = coded(&keys, 3).expect("both columns are read as places");
assert_eq!(coded.combos(), 15);
assert_eq!(placed(&coded, 3), [0, 1 + 5, 0]);
}
#[test]
fn a_null_row_of_a_packed_column_takes_the_place_past_the_last_code() {
let column = packed_numbers(&[1, 2, 1], 3, 1).with_validity(rudb_vector::Validity::Mask({
let mut mask = rudb_vector::Bitmap::all_valid(3);
mask.set(1, false);
mask
}));
let keys = [column];
let coded = coded(&keys, 3).expect("one narrow packed column is read as codes");
assert_eq!(coded.combos(), 9);
assert_eq!(placed(&coded, 3), [0, 8, 0]);
}
#[test]
fn a_filtered_packed_column_is_read_at_the_width_of_the_page_it_came_from() {
let page = packed_numbers(&[1, 2, 7, 1, 2, 7], 3, 1);
let kept = Vector::dictionary(vec![4, 0, 2], page).expect("the rows a filter kept");
let keys = [kept];
let coded = coded(&keys, 3).expect("a filtered narrow packed column is read as codes");
assert_eq!(coded.combos(), 9);
assert_eq!(placed(&coded, 3), [1, 0, 6]);
}
#[test]
fn a_filtered_chunk_and_a_whole_one_off_the_same_page_keep_one_map() {
let page = || packed_numbers(&[1, 2, 7, 1, 2, 7], 3, 1);
let first = [Vector::dictionary(vec![0, 1], page()).expect("the rows a filter kept")];
let over_first = coded(&first, 2).expect("codes");
let mut held = Vec::new();
over_first.hold(&mut held);
let next =
[Vector::dictionary(vec![3, 5], page()).expect("another chunk of the same page")];
assert!(coded(&next, 2).expect("codes").same_as(&held), "the same page, cut again");
let whole = [page()];
assert!(coded(&whole, 2).expect("codes").same_as(&held), "the same page, not cut at all");
let elsewhere = [Vector::dictionary(vec![0, 1], packed_numbers(&[9, 10], 3, 9))
.expect("a chunk of another page")];
assert!(!coded(&elsewhere, 2).expect("codes").same_as(&held), "another base");
}
#[test]
fn a_null_in_the_page_behind_a_filter_takes_the_place_past_the_last_code() {
let page = packed_numbers(&[1, 2, 7], 3, 1).with_validity(rudb_vector::Validity::Mask({
let mut mask = rudb_vector::Bitmap::all_valid(3);
mask.set(1, false);
mask
}));
let keys = [Vector::dictionary(vec![2, 1, 0], page).expect("the rows a filter kept")];
let coded = coded(&keys, 3).expect("codes");
assert_eq!(coded.combos(), 9);
assert_eq!(placed(&coded, 3), [6, 8, 0]);
}
#[test]
fn a_packed_column_wider_than_the_map_allows_is_refused() {
let keys = [packed_numbers(&[0, 1], 12, 0)];
assert!(coded(&keys, 2).is_none());
}
#[test]
fn a_chunk_packed_against_another_base_does_not_keep_the_map() {
let first = [packed_numbers(&[1, 2], 3, 1)];
let over_first = coded(&first, 2).expect("codes");
let mut held = Vec::new();
over_first.hold(&mut held);
assert!(over_first.same_as(&held));
let again = [packed_numbers(&[1, 2], 3, 1)];
assert!(coded(&again, 2).expect("codes").same_as(&held), "the same base and width");
let rebased = [packed_numbers(&[9, 10], 3, 9)];
assert!(!coded(&rebased, 2).expect("codes").same_as(&held), "another base");
let widened = [packed_numbers(&[1, 2], 4, 1)];
assert!(!coded(&widened, 2).expect("codes").same_as(&held), "another width");
let letters = [coded_letters(vec![0, 1], &["A", "N"])];
assert!(!coded(&letters, 2).expect("codes").same_as(&held), "another form entirely");
}
fn wide_dictionary(entries: usize) -> Vector {
let values: Vec<Value> = (0..entries as i32).map(Value::Integer).collect();
Vector::dictionary(vec![0, 1], flat(LogicalType::Integer, &values))
.expect("a dictionary of that many values")
}
#[test]
fn a_dictionary_wider_than_the_map_allows_is_refused() {
let keys = [wide_dictionary(WIDE_COMBOS)];
assert!(coded(&keys, 2).is_none());
}
#[test]
fn one_dictionary_the_size_of_a_row_groups_is_read_as_codes() {
let keys = [wide_dictionary(128_000)];
let coded = coded(&keys, 2).expect("one wide dictionary is read as codes");
assert_eq!(coded.combos(), 128_001);
assert_eq!(placed(&coded, 2), [0, 1]);
}
#[test]
fn two_dictionaries_that_wide_are_refused_even_though_one_would_not_be() {
let keys = [wide_dictionary(128_000), wide_dictionary(4)];
assert!(coded(&keys, 2).is_none());
let narrow = [wide_dictionary(100), wide_dictionary(4)];
assert!(coded(&narrow, 2).is_some(), "their product is still inside the small bound");
}
#[test]
fn a_wide_dictionary_that_might_hold_a_null_is_refused() {
let column = wide_dictionary(128_000).with_validity(rudb_vector::Validity::Mask({
let mut mask = rudb_vector::Bitmap::all_valid(2);
mask.set(1, false);
mask
}));
assert!(coded(&[column], 2).is_none());
let narrow = wide_dictionary(100).with_validity(rudb_vector::Validity::Mask({
let mut mask = rudb_vector::Bitmap::all_valid(2);
mask.set(1, false);
mask
}));
let keys = [narrow];
let coded = coded(&keys, 2).expect("a narrow one is still scanned");
assert_eq!(placed(&coded, 2), [0, 100], "the null row takes the place past the last code");
}
#[test]
fn a_chunk_under_other_dictionaries_does_not_keep_the_map() {
let first = [coded_letters(vec![0, 1], &["A", "N"])];
let over_first = coded(&first, 2).expect("codes");
let mut held = Vec::new();
over_first.hold(&mut held);
assert!(over_first.same_as(&held));
let second = [coded_letters(vec![0, 1], &["A", "N"])];
let later = coded(&second, 2).expect("codes");
assert!(!later.same_as(&held), "a dictionary built again is not the one the map holds");
assert!(coded(&[], 0).is_none(), "no key columns are no codes");
}
fn integers(values: &[Option<i32>]) -> Vector {
let values: Vec<Value> =
values.iter().map(|value| value.map_or(Value::Null, Value::Integer)).collect();
flat(LogicalType::Integer, &values)
}
#[test]
fn runs_found_by_the_window_are_the_runs_of_the_places() {
let lengths = [(10, 5), (12, 130), (10, 1), (11, 40), (20, 124), (12, 16)];
let rows: usize = lengths.iter().map(|&(_, length)| length).sum();
let sorted: Vec<Option<i32>> = lengths
.iter()
.flat_map(|&(value, length)| std::iter::repeat_n(Some(value), length))
.collect();
let keys = [integers(&sorted)];
let mut values = Widened::default();
let coded = coded_within(&keys, rows, &[], Some(&mut values)).expect("read by value");
let mut places = Vec::new();
coded.places(rows, &mut places);
let mut expected = Vec::new();
for (row, &place) in places.iter().enumerate() {
if row + 1 == rows || places[row + 1] != place {
expected.push((place, row + 1));
}
}
let mut runs = Vec::new();
assert!(coded.place_runs(rows, rows, &mut runs));
assert_eq!(runs, expected);
assert_eq!(runs.len(), lengths.len());
assert!(!coded.place_runs(rows, 2, &mut runs));
let scattered: Vec<Option<i32>> = (0..rows as i32).map(|row| Some(row * 7 % 13)).collect();
let keys = [integers(&scattered)];
let coded = coded_within(&keys, rows, &[], Some(&mut values)).expect("read by value");
assert!(!coded.place_runs(rows, rows / 8, &mut runs));
assert!(runs.is_empty());
}
#[test]
fn a_flat_column_is_read_by_its_value_against_a_window() {
let keys = [integers(&[Some(62), Some(1_000), Some(62), Some(-5)])];
let mut values = Widened::default();
let coded = coded_within(&keys, 4, &[], Some(&mut values)).expect("read by value");
let places = placed(&coded, 4);
assert_eq!(places[0], places[2], "one value is one place");
assert_eq!(places.iter().collect::<std::collections::HashSet<_>>().len(), 3);
assert!(
places.iter().all(|&place| place < coded.combos() - 1),
"no value takes the null place"
);
assert_eq!(
places[1] - places[3],
1_005,
"a place is the value less the bottom of the window"
);
}
#[test]
fn a_window_is_kept_for_as_long_as_the_chunks_land_inside_it() {
let mut values = Widened::default();
let first = [integers(&[Some(100), Some(200)])];
let mut held = Vec::new();
coded_within(&first, 2, &[], Some(&mut values)).expect("read by value").hold(&mut held);
let inside = [integers(&[Some(150), Some(101)])];
let again = coded_within(&inside, 2, &held, Some(&mut values)).expect("read by value");
assert!(again.same_as(&held), "a chunk inside the window keeps the map");
let past = [integers(&[Some(90_000), Some(100)])];
let wider = coded_within(&past, 2, &held, Some(&mut values)).expect("read by value");
assert!(!wider.same_as(&held), "a chunk past the window builds a new one");
let mut grown = Vec::new();
wider.hold(&mut grown);
let back = [integers(&[Some(200), Some(90_000)])];
assert!(
coded_within(&back, 2, &grown, Some(&mut values))
.expect("read by value")
.same_as(&grown),
"and the new one still covers where the old one was"
);
}
#[test]
fn a_null_row_read_by_value_takes_the_place_past_the_window() {
let mut values = Widened::default();
let keys = [integers(&[Some(7), None, Some(9), None])];
let coded = coded_within(&keys, 4, &[], Some(&mut values)).expect("read by value");
let places = placed(&coded, 4);
assert_eq!(places[1], coded.combos() - 1);
assert_eq!(places[3], coded.combos() - 1);
assert_eq!(places[2] - places[0], 2);
let mut held = Vec::new();
coded.hold(&mut held);
let null_place = coded.combos() - 1;
let nothing = [integers(&[None, None])];
let over = coded_within(¬hing, 2, &held, Some(&mut values)).expect("read by value");
assert!(over.same_as(&held));
assert_eq!(placed(&over, 2), [null_place; 2]);
}
#[test]
fn values_spread_wider_than_the_map_allows_are_refused() {
let mut values = Widened::default();
let wide = [integers(&[Some(0), Some(WIDE_COMBOS as i32)])];
assert!(coded_within(&wide, 2, &[], Some(&mut values)).is_none());
let fits = [integers(&[Some(0), Some(WIDE_COMBOS as i32 - 2)])];
assert!(coded_within(&fits, 2, &[], Some(&mut values)).is_some());
let pair = [integers(&[Some(0), Some(100)]), integers(&[Some(0), Some(100)])];
assert!(coded_within(&pair, 2, &[], Some(&mut values)).is_none());
let small = [integers(&[Some(0), Some(10)]), integers(&[Some(0), Some(10)])];
let coded = coded_within(&small, 2, &[], Some(&mut values)).expect("a product of 144");
let places = placed(&coded, 2);
assert_ne!(places[0], places[1]);
}
#[test]
fn a_filtered_packed_column_too_wide_for_places_is_read_by_value() {
let page = packed_numbers(&[17, 262_029, 62, 62], 18, 17);
let kept = [Vector::dictionary(vec![2, 1, 3], page).expect("the rows a filter kept")];
assert!(coded(&kept, 3).is_none(), "eighteen bits is too wide for places");
let mut values = Widened::default();
let coded = coded_within(&kept, 3, &[], Some(&mut values)).expect("read by value");
let places = placed(&coded, 3);
assert_eq!(places[0], places[2]);
assert_eq!(places[1] - places[0], 262_029 - 62);
}
#[test]
fn a_selection_over_a_flat_column_is_read_by_value_once_it_moves() {
let chunk = |values: &[Option<i32>], kept: Vec<u32>| {
[Vector::dictionary(kept, integers(values)).expect("the rows a filter kept")]
};
let mut values = Widened::default();
let mut held = Vec::new();
let first = chunk(&[Some(5), Some(9), None, Some(5)], vec![0, 2, 3]);
let coded = coded_within(&first, 3, &held, Some(&mut values)).expect("codes of its own");
coded.hold(&mut held);
let second = chunk(&[Some(1), Some(7), Some(5), None, Some(7)], vec![1, 2, 3, 4]);
let coded = coded_within(&second, 4, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value());
let places = placed(&coded, 4);
assert_eq!(places[0], places[3]);
assert_eq!(places[0] - places[1], 2);
assert_eq!(places[2], coded.combos() - 1, "the null takes the place past the window");
coded.hold(&mut held);
let third = chunk(&[Some(6), Some(8)], vec![0, 1]);
let coded = coded_within(&third, 2, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value() && coded.same_as(&held), "the window outlives the chunk");
let back = [Vector::dictionary(vec![1, 0], integers(&[Some(3), Some(4)])).expect("codes")];
let coded = coded_within(&back, 2, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value() && coded.same_as(&held), "codes that go back fit the window");
let places = placed(&coded, 2);
assert_eq!(places[0], places[1] + 1, "4 sits one place past 3");
let text = |words: &[&str]| {
let words: Vec<Value> = words.iter().map(|&word| Value::Varchar(word.into())).collect();
[Vector::dictionary(vec![1, 0, 1], flat(LogicalType::Varchar, &words)).expect("codes")]
};
let first = text(&["a", "b"]);
coded_within(&first, 3, &[], Some(&mut values)).expect("codes").hold(&mut held);
let next = text(&["c", "d"]);
let coded = coded_within(&next, 3, &held, Some(&mut values)).expect("codes of its own");
assert!(!coded.by_value(), "a string column keeps its codes");
}
#[test]
fn a_packed_page_other_than_the_one_held_is_read_by_value() {
let first = [packed_numbers(&[100, 101, 102, 101], 2, 100)];
let mut values = Widened::default();
let mut held = Vec::new();
let coded = coded_within(&first, 4, &held, Some(&mut values)).expect("places of its own");
assert!(!coded.by_value(), "a first page keeps its codes");
coded.hold(&mut held);
let same = [packed_numbers(&[102, 100, 100, 103], 2, 100)];
let coded = coded_within(&same, 4, &held, Some(&mut values)).expect("places of its own");
assert!(!coded.by_value() && coded.same_as(&held), "the same base keeps the map");
let next = [packed_numbers(&[104, 105, 106, 104], 2, 104)];
let coded = coded_within(&next, 4, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value());
let places = placed(&coded, 4);
assert_eq!(places[0], places[3]);
assert_eq!(places[2] - places[0], 2);
coded.hold(&mut held);
let after = [packed_numbers(&[107, 105, 104, 106], 2, 104)];
let coded = coded_within(&after, 4, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value() && coded.same_as(&held), "the window outlives the page");
let filtered =
[Vector::dictionary(vec![3, 0], packed_numbers(&[108, 109, 110, 111], 2, 108))
.expect("the rows a filter kept")];
let coded = coded_within(&filtered, 2, &held, Some(&mut values)).expect("read by value");
assert!(coded.by_value() && coded.same_as(&held), "a filtered page lands in it too");
assert_eq!(placed(&coded, 2)[0] - placed(&coded, 2)[1], 3);
let wide = [packed_numbers(&[0, 1 << 20, 0, 1], 21, 0)];
assert!(coded_within(&wide, 4, &held, Some(&mut values)).is_none(), "no places either");
}
#[test]
fn a_row_hashed_by_value_is_the_row_hashed_with_its_chunk() {
let page = packed_numbers(&[17, 262_029, 62, 62, -4], 18, -4);
let forms: Vec<Vec<Vector>> = vec![
vec![integers(&[Some(62), None, Some(-7), Some(62)])],
vec![packed_numbers(&[3, 900, 3, 70_000], 17, 3)],
vec![Vector::dictionary(vec![4, 1, 2, 3], page).expect("the rows a filter kept")],
vec![
integers(&[Some(1), Some(2), None, Some(1)]),
integers(&[Some(5), None, Some(5), Some(9)]),
],
];
for keys in &forms {
let mut values = Widened::default();
let coded = coded_within(keys, 4, &[], Some(&mut values)).expect("read by value");
assert!(coded.by_value());
let mut whole = Vec::new();
hash(keys, 4, &mut whole, Across::OneInput);
let alone: Vec<u64> = (0..4).map(|row| coded.hash_of(row)).collect();
assert_eq!(alone, whole, "{keys:?}");
}
}
fn marked(keys: &[Vector], rows: usize) -> Vec<bool> {
let mut same = Vec::new();
let count = repeats(keys, rows, 0, &mut same);
assert_eq!(count, same.iter().filter(|&&flag| flag).count(), "the count is what is marked");
same
}
#[test]
fn a_row_is_marked_when_the_row_before_it_holds_the_same_key() {
let column = flat(
LogicalType::Integer,
&[Value::Integer(7), Value::Integer(7), Value::Integer(8), Value::Integer(7)],
);
assert_eq!(marked(std::slice::from_ref(&column), 4), [false, true, false, false]);
}
#[test]
fn a_row_is_marked_only_when_every_key_column_repeats() {
let left =
flat(LogicalType::Integer, &[Value::Integer(1), Value::Integer(1), Value::Integer(1)]);
let right = flat(
LogicalType::Varchar,
&[Value::Varchar("a".into()), Value::Varchar("a".into()), Value::Varchar("b".into())],
);
assert_eq!(marked(&[left, right], 3), [false, true, false]);
}
#[test]
fn two_nulls_next_to_each_other_are_the_same_key_and_a_null_beside_a_value_is_not() {
let column =
flat(LogicalType::Integer, &[Value::Null, Value::Null, Value::Integer(4), Value::Null]);
assert_eq!(marked(std::slice::from_ref(&column), 4), [false, true, false, false]);
}
#[test]
fn a_dictionary_is_read_through_its_codes() {
let column = coded_letters(vec![0, 0, 1, 1, 0], &["A", "N"]);
assert_eq!(marked(std::slice::from_ref(&column), 5), [false, true, false, true, false]);
}
#[test]
fn a_float_column_gives_up_rather_than_deciding_what_counts_as_equal() {
let column = flat(LogicalType::Double, &[Value::Double(1.0), Value::Double(1.0)]);
assert_eq!(marked(std::slice::from_ref(&column), 2), [false, false]);
}
#[test]
fn a_chunk_with_too_few_repeats_for_the_caller_is_dropped() {
let column = flat(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(1), Value::Integer(2), Value::Integer(3)],
);
let keys = std::slice::from_ref(&column);
let mut same = Vec::new();
assert_eq!(repeats(keys, 4, 2, &mut same), 0);
assert_eq!(same, [false, false, false, false]);
assert_eq!(repeats(keys, 4, 1, &mut same), 1);
assert_eq!(repeats(&[], 0, 0, &mut same), 0);
}
fn stable_letters(codes: Vec<u32>, seen: &[&str], nulls: &[usize]) -> Vector {
let values: Vec<Value> = seen.iter().map(|word| Value::Varchar((*word).into())).collect();
let rows = codes.len();
let vector =
Vector::stable_dictionary(codes, Arc::new(flat(LogicalType::Varchar, &values)))
.expect("a stable dictionary of those values");
vector.with_validity(rudb_vector::Validity::from_iter(rows, |row| !nulls.contains(&row)))
}
#[test]
fn a_null_row_of_a_stable_dictionary_stays_in_the_run() {
let column = stable_letters(vec![0, 0, 1], &["a", "bb"], &[1]);
let mut key = Column::new(&LogicalType::Varchar);
for row in 0..3 {
key.push_from(&column, row).expect("every row goes in");
}
assert!(matches!(key.data, StoredData::StableText { .. }), "a null did not end the run");
assert_eq!(
key.values(0..3),
[Value::Varchar("a".into()), Value::Null, Value::Varchar("bb".into()),]
);
let vector = key.vector(&LogicalType::Varchar, 0..3).expect("the run comes back");
assert!(vector.is_null_at(1));
assert_eq!(vector.bytes_at(2), Some(b"bb".as_slice()));
}
#[test]
fn a_run_that_starts_on_a_null_holds_the_rows_after_it() {
let column = stable_letters(vec![0, 0, 1], &["a", "bb"], &[0]);
let mut key = Column::new(&LogicalType::Varchar);
for row in 0..3 {
key.push_from(&column, row).expect("every row goes in");
}
assert_eq!(
key.values(0..3),
[Value::Null, Value::Varchar("a".into()), Value::Varchar("bb".into()),]
);
}
#[test]
fn a_row_under_another_dictionary_ends_the_run_and_keeps_what_it_held() {
let first = stable_letters(vec![0, 1, 0], &["a", "bb"], &[1]);
let second = stable_letters(vec![1], &["a", "cc"], &[]);
let mut key = Column::new(&LogicalType::Varchar);
for row in 0..3 {
key.push_from(&first, row).expect("every row goes in");
}
key.push_from(&second, 0).expect("the row under the other dictionary goes in too");
assert!(matches!(key.data, StoredData::Varchar(_)), "the run ended");
assert_eq!(
key.values(0..4),
[
Value::Varchar("a".into()),
Value::Null,
Value::Varchar("a".into()),
Value::Varchar("cc".into()),
]
);
}
#[test]
fn a_stable_dictionary_groups_the_same_as_the_flat_column_it_stands_for() {
let values = [
Value::Varchar("a".into()),
Value::Null,
Value::Varchar("a".into()),
Value::Varchar(String::new()),
Value::Null,
Value::Varchar("bb".into()),
];
let types = [LogicalType::Varchar];
let run = [stable_letters(vec![0, 0, 0, 1, 0, 2], &["a", "", "bb"], &[1, 4])];
let plain = [flat(LogicalType::Varchar, &values)];
let (over_run, run_slots) = one_at_a_time(&run, values.len(), &types);
let (over_plain, plain_slots) = one_at_a_time(&plain, values.len(), &types);
assert_eq!(run_slots, plain_slots);
assert_eq!(over_run.len(), over_plain.len());
let (batched, batched_slots) = a_batch_at_a_time(&run, values.len(), &types);
assert_eq!(batched_slots, plain_slots);
assert_eq!(batched.len(), over_plain.len());
}
#[test]
fn the_runs_of_a_cut_key_are_those_of_the_rows_the_filter_kept() {
let values: Vec<i32> = [5, 7, 9, 2].iter().flat_map(|&value| [value; 40]).collect();
let column = Vector::flat(LogicalType::Integer, Data::Int32(values.into())).unwrap();
let (mut into, mut runs) = (Vec::new(), Vec::new());
let kept: Vec<u32> = (0..160).step_by(2).filter(|row| !(90..130).contains(row)).collect();
let cut = Vector::dictionary(kept.clone(), column.clone()).unwrap();
assert!(selected_runs(&cut, kept.len(), &mut into, &mut runs));
let wanted: Vec<i64> = kept.iter().map(|&row| [5, 7, 9, 2][row as usize / 40]).collect();
assert_eq!(into, wanted);
assert_eq!(runs, [(5, 20), (7, 40), (9, 45), (2, 60)]);
let kept: Vec<u32> = (0..40).chain(80..160).collect();
let cut = Vector::dictionary(kept.clone(), column.clone()).unwrap();
assert!(selected_runs(&cut, kept.len(), &mut into, &mut runs));
assert_eq!(runs, [(5, 40), (9, 80), (2, 120)]);
let backwards = Vector::dictionary((0..40).rev().collect(), column).unwrap();
assert!(!selected_runs(&backwards, 40, &mut into, &mut runs));
assert!(runs.is_empty());
}
#[test]
fn rows_below_an_end_match_a_search() {
let every: Vec<u32> = (10..60).collect();
let gaps: Vec<u32> =
(10..200).filter(|row| row % 7 != 3 && !(40..90).contains(row)).collect();
for at in [&every[..], &gaps[..], &[][..], &[5][..]] {
for end in 0..220 {
let wanted = at.partition_point(|&row| (row as usize) < end);
assert_eq!(below(at, end), wanted, "{end} in {at:?}");
}
}
}
}