use std::collections::HashSet;
use std::sync::{Arc, OnceLock};
use rudb_common::{LogicalType, Result, Value};
use rudb_vector::{Data, Form, Live, Selection, Validity, Vector};
use crate::fallback::{self, Kernel};
use crate::peel::{Found, Peel, search};
use crate::shape::{first, identity, nulls_of, single};
#[derive(Debug)]
pub struct Members {
held: Held,
has_null: bool,
negated: bool,
sought: OnceLock<Sought>,
peel: Peel,
}
#[derive(Debug)]
struct Sought {
dictionary: Arc<Vector>,
hits: Vec<bool>,
}
#[derive(Debug)]
enum Held {
Whole(Whole),
Text(Text),
}
#[derive(Debug)]
struct Whole {
short: Vec<i128>,
set: HashSet<i128>,
}
const SHORT: usize = 8;
impl Whole {
fn of(set: HashSet<i128>) -> Self {
if set.len() <= SHORT {
Self { short: set.into_iter().collect(), set: HashSet::new() }
} else {
Self { short: Vec::new(), set }
}
}
#[inline]
fn contains(&self, value: &i128) -> bool {
if self.set.is_empty() { self.short.contains(value) } else { self.set.contains(value) }
}
fn len(&self) -> usize {
self.short.len() + self.set.len()
}
}
#[derive(Debug)]
struct Text {
words: Vec<(u64, usize)>,
short: Vec<Box<[u8]>>,
set: HashSet<Box<[u8]>>,
}
#[inline]
fn word(value: &[u8]) -> u64 {
let mut bytes = [0_u8; 8];
bytes[..value.len()].copy_from_slice(value);
u64::from_le_bytes(bytes)
}
impl Text {
fn of(set: HashSet<String>) -> Self {
let held = set.into_iter().map(|text| text.into_bytes().into_boxed_slice());
if held.len() > SHORT {
return Self { words: Vec::new(), short: Vec::new(), set: held.collect() };
}
let short: Vec<Box<[u8]>> = held.collect();
if short.iter().all(|text| text.len() <= 8) {
let words = short.iter().map(|text| (word(text), text.len())).collect();
return Self { words, short, set: HashSet::new() };
}
Self { words: Vec::new(), short, set: HashSet::new() }
}
#[inline]
fn contains(&self, value: &[u8]) -> bool {
if !self.words.is_empty() {
if value.len() > 8 {
return false;
}
let value = (word(value), value.len());
return self.words.contains(&value);
}
if self.set.is_empty() {
self.short.iter().any(|held| **held == *value)
} else {
self.set.contains(value)
}
}
fn iter(&self) -> impl Iterator<Item = &[u8]> {
self.short.iter().chain(self.set.iter()).map(|held| &**held)
}
fn len(&self) -> usize {
self.short.len() + self.set.len()
}
}
impl Members {
#[must_use]
pub fn of(values: &[Value], negated: bool) -> Option<Self> {
if values.len() < 2 {
return None;
}
let mut whole: HashSet<i128> = HashSet::new();
let mut text: HashSet<String> = HashSet::new();
let mut has_null = false;
let mut kind: Option<std::mem::Discriminant<Value>> = None;
for value in values {
if matches!(value, Value::Null) {
has_null = true;
continue;
}
let held = std::mem::discriminant(value);
if *kind.get_or_insert(held) != held {
return None;
}
match value {
Value::Varchar(held) => {
text.insert(held.clone());
}
other => {
whole.insert(number(other)?);
}
}
}
let held = if text.is_empty() {
if whole.is_empty() {
return None;
}
Held::Whole(Whole::of(whole))
} else {
Held::Text(Text::of(text))
};
Some(Self { held, has_null, negated, sought: OnceLock::new(), peel: Peel::default() })
}
fn sought(&self, column: &Vector) -> Option<Result<&[bool]>> {
let Held::Text(set) = &self.held else { return None };
let (_, dictionary) = column.shared_dictionary_parts()?;
if self.sought.get().is_none() {
let ranks = dictionary.ranks()?;
let mut hits = vec![false; dictionary.len()];
for text in set.iter() {
match search(dictionary, ranks, text) {
Ok(Found::At(code)) => {
if let Some(hit) = hits.get_mut(code as usize) {
*hit = true;
}
}
Ok(Found::Absent) => {}
Err(error) => return Some(Err(error)),
}
}
let _ = self.sought.set(Sought { dictionary: Arc::clone(dictionary), hits });
}
let memo = self.sought.get()?;
Arc::ptr_eq(&memo.dictionary, dictionary).then_some(Ok(memo.hits.as_slice()))
}
fn at_code(&self, dictionary: &Vector, code: usize) -> Result<bool> {
Ok(match &self.held {
Held::Text(set) => {
dictionary.try_bytes_at(code)?.is_some_and(|text| set.contains(text))
}
Held::Whole(set) => {
number(&dictionary.try_value_at(code)?).is_some_and(|held| set.contains(&held))
}
})
}
#[must_use]
pub fn len(&self) -> usize {
match &self.held {
Held::Whole(set) => set.len(),
Held::Text(set) => set.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
fn number(value: &Value) -> Option<i128> {
match *value {
Value::TinyInt(held) => Some(i128::from(held)),
Value::SmallInt(held) => Some(i128::from(held)),
Value::Integer(held) | Value::Date(held) => Some(i128::from(held)),
Value::BigInt(held)
| Value::Time(held)
| Value::TimeTz(held)
| Value::Timestamp(held)
| Value::TimestampTz(held) => Some(i128::from(held)),
Value::HugeInt(held) => Some(held),
Value::UTinyInt(held) => Some(i128::from(held)),
Value::USmallInt(held) => Some(i128::from(held)),
Value::UInteger(held) => Some(i128::from(held)),
Value::UBigInt(held) => Some(i128::from(held)),
_ => None,
}
}
pub fn in_set(input: &Vector, members: &Members, returns: &LogicalType) -> Result<Vector> {
let rows = input.len();
let base = nulls_of(input);
match input.form() {
Form::Flat => match input.data() {
Some(data) => look(data, identity, members, &base, rows, returns),
None => row_at_a_time(input, members, &base, rows, returns),
},
Form::Dictionary | Form::Rle => {
if let Some(found) = members.sought(input) {
let found = found?;
let (codes, _) = input.shared_dictionary_parts().ok_or_else(|| {
rudb_common::Error::internal("a searched column lost its codes")
})?;
if codes.len() >= rows {
return answer(rows, &base, members, returns, |index| {
found.get(codes[index] as usize).copied().unwrap_or(false)
});
}
}
if let Some(found) = members
.peel
.answer(input, rows, identity, |dictionary, code| members.at_code(dictionary, code))
{
let found = found?;
return answer(rows, &base, members, returns, |index| found[index]);
}
let Some((codes, values)) = input.positions() else {
return row_at_a_time(input, members, &base, rows, returns);
};
if codes.len() < rows {
return row_at_a_time(input, members, &base, rows, returns);
}
let at = move |index: usize| codes[index] as usize;
match (values.data(), values.packed_parts()) {
(Some(data), _) => look(data, at, members, &base, rows, returns),
(None, Some(packed)) => packed_look(&packed, at, members, &base, rows, returns),
(None, None) => row_at_a_time(input, members, &base, rows, returns),
}
}
Form::BitPacked => match input.packed_parts() {
Some(packed) => packed_look(&packed, identity, members, &base, rows, returns),
None => row_at_a_time(input, members, &base, rows, returns),
},
Form::Constant => {
let Some(value) = input.constant_value() else {
return row_at_a_time(input, members, &base, rows, returns);
};
let Some(held) = single(input.logical_type(), value) else {
return row_at_a_time(input, members, &base, rows, returns);
};
match held.data() {
Some(data) => look(data, first, members, &base, rows, returns),
None => row_at_a_time(input, members, &base, rows, returns),
}
}
_ => row_at_a_time(input, members, &base, rows, returns),
}
}
#[must_use]
pub fn select_in(input: &Vector, members: &Members, live: Option<&Selection>) -> Option<Selection> {
let set = match &members.held {
Held::Whole(set) => set,
Held::Text(_) => return select_text(input, members, live),
};
if members.has_null || !set.set.is_empty() || !input.none_null() {
return None;
}
let rows = input.len();
u32::try_from(rows).ok()?;
let negated = members.negated;
let named = live.map(Selection::indices);
if named.is_some_and(|named| named.iter().any(|&row| row as usize >= rows)) {
return None;
}
let count = named.map_or(rows, <[u32]>::len);
macro_rules! flat {
($values:expr, $ty:ty) => {{
let values = $values.as_slice();
let wanted: Vec<$ty> =
set.short.iter().filter_map(|&value| <$ty>::try_from(value).ok()).collect();
Some(match named {
Some(named) => chosen(|slot| values[named[slot] as usize], &wanted, negated, named),
None => chosen_all(values, &wanted, negated),
})
}};
}
match input.form() {
Form::Flat => match input.data()? {
Data::Int8(values) => flat!(values, i8),
Data::Int16(values) => flat!(values, i16),
Data::Int32(values) => flat!(values, i32),
Data::Int64(values) => flat!(values, i64),
Data::UInt8(values) => flat!(values, u8),
Data::UInt16(values) => flat!(values, u16),
Data::UInt32(values) => flat!(values, u32),
Data::UInt64(values) => flat!(values, u64),
_ => None,
},
Form::BitPacked => {
let packed = input.packed_parts()?;
let wanted: Vec<u64> =
set.short.iter().filter_map(|&value| packed.code_of(value)).collect();
Some(match named {
Some(named) => {
let codes = packed.codes_at(|slot| named[slot] as usize, count);
chosen(|slot| codes[slot], &wanted, negated, named)
}
None => {
let mut codes = vec![0; rows];
packed.unpack(0, &mut codes);
chosen_all(&codes, &wanted, negated)
}
})
}
_ => None,
}
}
fn select_text(input: &Vector, members: &Members, live: Option<&Selection>) -> Option<Selection> {
if members.has_null || !input.none_null() || !matches!(input.form(), Form::Dictionary) {
return None;
}
let rows = input.len();
u32::try_from(rows).ok()?;
let named = live.map(Selection::indices);
let (codes, _) = input.shared_dictionary_parts()?;
if codes.len() < rows
|| named.is_some_and(|named| named.iter().any(|&row| row as usize >= rows))
{
return None;
}
let count = named.map_or(rows, <[u32]>::len);
#[expect(clippy::cast_possible_truncation, reason = "the row count was checked to fit a u32")]
let row = |slot: usize| named.map_or(slot as u32, |named| named[slot]);
let negated = members.negated;
if let Some(found) = members.sought(input) {
let found = found.ok()?;
return Some(picked(count, row, |slot| {
found.get(codes[row(slot) as usize] as usize).copied().unwrap_or(false) != negated
}));
}
let flags = members
.peel
.answer(
input,
count,
|slot| row(slot) as usize,
|dictionary, code| members.at_code(dictionary, code),
)?
.ok()?;
Some(picked(count, row, |slot| flags[slot] != negated))
}
fn picked(count: usize, row: impl Fn(usize) -> u32, keep: impl Fn(usize) -> bool) -> Selection {
let mut out = vec![0_u32; count];
let mut kept = 0;
for slot in 0..count {
out[kept] = row(slot);
kept += usize::from(keep(slot));
}
out.truncate(kept);
Selection::from_indices(out)
}
#[expect(
clippy::cast_possible_truncation,
reason = "the caller checked that the row count fits in a u32"
)]
fn chosen_all<T: Copy + PartialEq>(values: &[T], wanted: &[T], negated: bool) -> Selection {
let mut out = vec![0_u32; values.len()];
let mut kept = 0;
for (row, &value) in values.iter().enumerate() {
out[kept] = row as u32;
kept += usize::from(among(value, wanted) != negated);
}
out.truncate(kept);
Selection::from_indices(out)
}
fn chosen<T: Copy + PartialEq>(
value: impl Fn(usize) -> T,
wanted: &[T],
negated: bool,
named: &[u32],
) -> Selection {
let mut out = vec![0_u32; named.len()];
let mut kept = 0;
for (slot, &row) in named.iter().enumerate() {
out[kept] = row;
kept += usize::from(among(value(slot), wanted) != negated);
}
out.truncate(kept);
Selection::from_indices(out)
}
#[inline]
fn among<T: Copy + PartialEq>(value: T, wanted: &[T]) -> bool {
match *wanted {
[] => false,
[one] => value == one,
[one, two] => (value == one) | (value == two),
[one, two, three] => (value == one) | (value == two) | (value == three),
_ => wanted.iter().fold(false, |found, &each| found | (value == each)),
}
}
fn look<A: Fn(usize) -> usize>(
data: &Data,
at: A,
members: &Members,
base: &Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Vector> {
match (&members.held, data) {
(Held::Text(set), Data::Varlen(column)) => answer(rows, base, members, returns, |index| {
column.bytes(at(index)).is_some_and(|text| set.contains(text))
}),
(Held::Whole(set), Data::Int8(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::Int16(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::Int32(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::Int64(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::Int128(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::UInt8(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::UInt16(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::UInt32(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
(Held::Whole(set), Data::UInt64(held)) => {
answer(rows, base, members, returns, |index| holds(set, held.as_slice(), at(index)))
}
_ => Err(rudb_common::Error::internal(format!(
"an IN list over a column this kernel does not read, which is {returns}"
))),
}
}
fn packed_look<A: Fn(usize) -> usize>(
packed: &rudb_vector::Packed<'_>,
at: A,
members: &Members,
base: &Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Vector> {
match &members.held {
Held::Whole(set) => answer(rows, base, members, returns, |index| {
set.contains(&(packed.base() + i128::from(packed.code(at(index)))))
}),
Held::Text(_) => answer(rows, base, members, returns, |_| false),
}
}
fn holds<T: Copy>(set: &Whole, values: &[T], index: usize) -> bool
where
i128: From<T>,
{
values.get(index).is_some_and(|&held| set.contains(&i128::from(held)))
}
fn answer(
rows: usize,
base: &Validity,
members: &Members,
returns: &LogicalType,
found: impl Fn(usize) -> bool,
) -> Result<Vector> {
if base.live() == Live::All && !members.has_null {
let out: Vec<bool> = (0..rows).map(|index| found(index) != members.negated).collect();
return Vector::flat(returns.clone(), Data::Bool(out.into()));
}
let mut out = vec![false; rows];
let mut live = vec![false; rows];
for index in 0..rows {
if !base.is_valid(index) {
continue;
}
let hit = found(index);
live[index] = hit || !members.has_null;
out[index] = hit != members.negated;
}
let validity = Validity::from_run(&live).normalize(rows);
Ok(Vector::flat(returns.clone(), Data::Bool(out.into()))?.with_validity(validity))
}
fn row_at_a_time(
input: &Vector,
members: &Members,
base: &Validity,
rows: usize,
returns: &LogicalType,
) -> Result<Vector> {
fallback::record(Kernel::Membership, input.form(), input.form());
let held: Vec<Value> =
(0..rows).map(|index| input.try_value_at(index)).collect::<Result<_>>()?;
answer(rows, base, members, returns, |index| match (&members.held, &held[index]) {
(Held::Text(set), Value::Varchar(text)) => set.contains(text.as_bytes()),
(Held::Whole(set), value) => number(value).is_some_and(|held| set.contains(&held)),
_ => false,
})
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering as Memory};
use rudb_common::{LogicalType, Value};
use rudb_vector::{Form, Selection, Vector};
use super::{Kernel, Members, fallback, in_set, select_in};
fn over(input: &Vector, list: &[Value], negated: bool) -> Vec<Value> {
let members = Members::of(list, negated).expect("this list folds");
let answer = in_set(input, &members, &LogicalType::Boolean).expect("the lookup runs");
(0..input.len()).map(|row| answer.value_at(row)).collect()
}
fn numbers() -> Vector {
Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(7), Value::Null, Value::Integer(3)],
)
.expect("four integers")
}
#[test]
fn a_text_list_matches_whole_values_whatever_their_length() {
let texts = ["13", "1", "", "130", "é", "éé", "exactly8", "exactly8!", "31"];
let input = Vector::from_values(
LogicalType::Varchar,
&texts.iter().map(|text| Value::Varchar((*text).into())).collect::<Vec<_>>(),
)
.expect("strings");
let text = |value: &str| Value::Varchar(value.into());
let lists: [&[&str]; 4] = [
&["13", "31", "é"],
&["", "exactly8", "éé"],
&["exactly8!", "1"],
&["a", "b", "c", "d", "e", "f", "g", "h", "13", "éé"],
];
for list in lists {
let values: Vec<Value> = list.iter().map(|value| text(value)).collect();
let wanted: Vec<Value> =
texts.iter().map(|value| Value::Boolean(list.contains(value))).collect();
assert_eq!(over(&input, &values, false), wanted, "{list:?}");
}
}
#[test]
fn a_row_in_the_list_is_true_and_a_row_outside_it_is_false() {
assert_eq!(
over(&numbers(), &[Value::Integer(1), Value::Integer(3)], false),
[Value::Boolean(true), Value::Boolean(false), Value::Null, Value::Boolean(true)]
);
}
#[test]
fn a_not_in_is_the_same_lookup_read_the_other_way() {
assert_eq!(
over(&numbers(), &[Value::Integer(1), Value::Integer(3)], true),
[Value::Boolean(false), Value::Boolean(true), Value::Null, Value::Boolean(false)]
);
}
#[test]
fn a_long_list_answers_what_a_short_one_does() {
let mut long: Vec<Value> = (100..120).map(Value::Integer).collect();
long.push(Value::Integer(3));
long.push(Value::Integer(1));
assert_eq!(
over(&numbers(), &long, false),
[Value::Boolean(true), Value::Boolean(false), Value::Null, Value::Boolean(true)]
);
}
#[test]
fn a_miss_against_a_list_with_a_null_in_it_is_null() {
let list = [Value::Integer(1), Value::Null, Value::Integer(3)];
assert_eq!(
over(&numbers(), &list, false),
[Value::Boolean(true), Value::Null, Value::Null, Value::Boolean(true)]
);
assert_eq!(
over(&numbers(), &list, true),
[Value::Boolean(false), Value::Null, Value::Null, Value::Boolean(false)]
);
}
#[test]
fn a_dictionary_column_is_read_through_its_codes() {
let values = Vector::from_values(
LogicalType::Varchar,
&[Value::Varchar("a".into()), Value::Varchar("b".into()), Value::Null],
)
.expect("builds");
let text = Vector::dictionary(vec![0, 2, 1, 0], values).expect("codes are in range");
let list = [Value::Varchar("a".into()), Value::Varchar("c".into())];
assert_eq!(
over(&text, &list, false),
[Value::Boolean(true), Value::Null, Value::Boolean(false), Value::Boolean(true)]
);
}
#[test]
fn a_packed_column_is_read_out_of_its_bits_rather_than_a_value_at_a_time() {
let values: Vec<Value> = (0..40)
.map(|row| if row % 9 == 0 { Value::Null } else { Value::Integer(row % 12 - 1) })
.collect();
let flat = Vector::from_values(LogicalType::Integer, &values).expect("forty integers");
let packed = flat.clone().bit_packed().expect("a range of twelve packs");
assert_eq!(packed.form(), Form::BitPacked);
let list = [Value::Integer(-1), Value::Integer(6)];
assert_eq!(over(&packed, &list, false), over(&flat, &list, false));
assert_eq!(over(&packed, &list, true), over(&flat, &list, true));
let codes: Vec<u32> = (0..24).map(|row| (row * 7) % 40).collect();
let over_packed = Vector::dictionary(codes.clone(), packed).expect("codes are in range");
let over_flat = Vector::dictionary(codes, flat).expect("codes are in range");
assert_eq!(over_packed.form(), Form::Dictionary);
assert_eq!(over(&over_packed, &list, false), over(&over_flat, &list, false));
assert_eq!(fallback::count(Kernel::Membership, Form::BitPacked, Form::BitPacked), 0);
assert_eq!(fallback::count(Kernel::Membership, Form::Dictionary, Form::Dictionary), 0);
}
#[test]
fn selecting_the_rows_in_a_list_keeps_what_the_flags_say() {
let values: Vec<Value> = (0..300).map(|row| Value::Integer(row % 12 - 1)).collect();
let flat = Vector::from_values(LogicalType::Integer, &values).expect("integers");
let packed = flat.clone().bit_packed().expect("a range of twelve packs");
assert_eq!(packed.form(), Form::BitPacked);
let tiny: Vec<Value> = (0..300)
.map(|row| Value::TinyInt(i8::try_from(row % 12 - 1).expect("small")))
.collect();
let tiny = Vector::from_values(LogicalType::TinyInt, &tiny).expect("tiny integers");
let live = Selection::from_indices((0..300).filter(|row| row % 5 != 2).collect());
let lists = [
vec![Value::Integer(-1), Value::Integer(6)],
vec![Value::Integer(3), Value::Integer(9), Value::Integer(4000)],
vec![Value::Integer(0), Value::Integer(1), Value::Integer(2), Value::Integer(10)],
];
for list in &lists {
for negated in [false, true] {
let members = Members::of(list, negated).expect("this list folds");
let yes = over(&flat, list, negated);
let kept = |row: usize| yes[row] == Value::Boolean(true);
let every = Selection::from_predicate(300, kept);
let among = Selection::from_indices(
live.indices().iter().copied().filter(|&row| kept(row as usize)).collect(),
);
for column in [&flat, &packed, &tiny] {
let all = select_in(column, &members, None).expect("a short whole list");
assert_eq!(all, every, "{list:?} negated {negated} over {:?}", column.form());
let some = select_in(column, &members, Some(&live)).expect("the same");
assert_eq!(some, among, "{list:?} negated {negated} over the live rows");
}
}
}
let nulled = Members::of(&[Value::Integer(1), Value::Null], false).expect("folds");
assert!(select_in(&flat, &nulled, None).is_none());
let members = Members::of(&lists[0], false).expect("folds");
assert!(select_in(&numbers(), &members, None).is_none());
}
#[test]
fn a_constant_column_answers_every_row_the_same() {
let held = Vector::constant(LogicalType::Integer, Value::Integer(3), 3);
let list = [Value::Integer(1), Value::Integer(3)];
assert_eq!(over(&held, &list, false), vec![Value::Boolean(true); 3]);
}
#[test]
fn a_run_length_column_reads_the_same_as_the_flat_one_it_stands_for() {
let flat = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(1), Value::Integer(7), Value::Integer(7)],
)
.expect("four integers");
let runs = flat.clone().run_encoded().expect("two runs");
let list = [Value::Integer(1), Value::Integer(3)];
assert_eq!(over(&runs, &list, false), over(&flat, &list, false));
}
#[test]
fn a_list_of_one_is_left_alone_because_a_comparison_is_already_that() {
assert!(Members::of(&[Value::Integer(1)], false).is_none());
}
#[test]
fn a_list_of_floats_does_not_fold() {
assert!(Members::of(&[Value::Double(1.0), Value::Double(2.0)], false).is_none());
}
#[test]
fn a_list_of_two_kinds_does_not_fold() {
let mixed = [Value::Integer(1), Value::Varchar("a".into())];
assert!(Members::of(&mixed, false).is_none());
}
#[test]
fn a_list_of_nothing_but_nulls_does_not_fold() {
assert!(Members::of(&[Value::Null, Value::Null], false).is_none());
}
#[derive(Debug)]
struct Filed {
values: Vec<Vec<u8>>,
order: Vec<u32>,
reads: AtomicUsize,
}
impl rudb_vector::TextSource for Filed {
fn len(&self) -> usize {
self.values.len()
}
fn bytes_at(&self, index: usize) -> rudb_common::Result<Option<&[u8]>> {
self.reads.fetch_add(1, Memory::Relaxed);
Ok(self.values.get(index).map(Vec::as_slice))
}
fn footprint(&self) -> usize {
self.values.iter().map(Vec::len).sum()
}
fn ranks(&self) -> Option<usize> {
Some(self.order.len())
}
fn compare_rank(&self, rank: usize, wanted: &[u8]) -> rudb_common::Result<Ordering> {
Ok(self.values[self.order[rank] as usize].as_slice().cmp(wanted))
}
fn code_at_rank(&self, rank: usize) -> rudb_common::Result<u32> {
Ok(self.order[rank])
}
}
fn filed(words: &[&str], codes: Vec<u32>) -> (Vector, Vector, Arc<Filed>) {
let values: Vec<Vec<u8>> = words.iter().map(|text| text.as_bytes().to_vec()).collect();
let mut order = (0..values.len() as u32).collect::<Vec<_>>();
order.sort_by(|&left, &right| values[left as usize].cmp(&values[right as usize]));
let source = Arc::new(Filed { values, order, reads: AtomicUsize::new(0) });
let dictionary = Arc::new(
Vector::external_text(LogicalType::Varchar, Arc::clone(&source) as Arc<_>)
.expect("a filed vector"),
);
let flat = Vector::from_values(
LogicalType::Varchar,
&codes
.iter()
.map(|&code| Value::Varchar(words[code as usize].into()))
.collect::<Vec<_>>(),
)
.expect("the same rows written out");
let column = Vector::stable_dictionary(codes, dictionary).expect("codes are in range");
(column, flat, source)
}
#[test]
fn a_sorted_dictionary_is_searched_once_and_no_value_is_read() {
let (column, flat, source) =
filed(&["AIR", "MAIL", "RAIL", "SHIP", "TRUCK"], vec![1, 0, 3, 4, 1, 2, 3]);
let list = [Value::Varchar("MAIL".into()), Value::Varchar("SHIP".into())];
assert_eq!(over(&column, &list, false), over(&flat, &list, false));
assert_eq!(over(&column, &list, true), over(&flat, &list, true));
assert_eq!(source.reads.load(Memory::Relaxed), 0);
}
#[test]
fn a_list_the_dictionary_does_not_hold_is_false_everywhere() {
let (column, flat, _) = filed(&["AIR", "MAIL", "SHIP"], vec![0, 1, 2, 1]);
let list = [Value::Varchar("BOAT".into()), Value::Varchar("CART".into())];
assert_eq!(over(&column, &list, false), over(&flat, &list, false));
assert_eq!(over(&column, &list, false), vec![Value::Boolean(false); 4]);
}
#[test]
fn a_list_the_dictionary_holds_some_of_answers_what_the_flat_column_answers() {
let (column, flat, _) = filed(&["AIR", "MAIL", "SHIP"], vec![0, 1, 2, 1, 0]);
let list = [Value::Varchar("MAIL".into()), Value::Varchar("BOAT".into())];
assert_eq!(over(&column, &list, false), over(&flat, &list, false));
let holed = column
.with_validity(rudb_vector::Validity::from_run(&[true, false, true, true, false]));
let holed_flat =
flat.with_validity(rudb_vector::Validity::from_run(&[true, false, true, true, false]));
assert_eq!(over(&holed, &list, false), over(&holed_flat, &list, false));
}
#[test]
fn a_text_list_over_a_dictionary_selects_what_the_flat_column_flags() {
let words = ["AIR", "MAIL", "RAIL", "SHIP", "TRUCK"];
let codes: Vec<u32> = (0..300_u32).map(|row| (row * 7 + row / 3) % 5).collect();
let (sorted, flat, source) = filed(&words, codes.clone());
let values = Vector::from_values(
LogicalType::Varchar,
&words.iter().map(|&word| Value::Varchar(word.into())).collect::<Vec<_>>(),
)
.expect("builds");
let unsorted =
Vector::stable_dictionary(codes, Arc::new(values)).expect("codes are in range");
let live = Selection::from_predicate(300, |row| row % 3 != 1);
let lists = [
vec![Value::Varchar("MAIL".into()), Value::Varchar("SHIP".into())],
vec![Value::Varchar("BOAT".into()), Value::Varchar("CART".into())],
];
for list in &lists {
for negated in [false, true] {
let yes = over(&flat, list, negated);
let kept = |row: usize| yes[row] == Value::Boolean(true);
let every = Selection::from_predicate(300, kept);
let among = Selection::from_indices(
live.indices().iter().copied().filter(|&row| kept(row as usize)).collect(),
);
for column in [&sorted, &unsorted] {
let members = Members::of(list, negated).expect("this list folds");
let some = select_in(column, &members, Some(&live)).expect("a dictionary");
assert_eq!(some, among, "{list:?} negated {negated} over the live rows");
let all = select_in(column, &members, None).expect("the same dictionary");
assert_eq!(all, every, "{list:?} negated {negated} over every row");
}
}
}
assert_eq!(source.reads.load(Memory::Relaxed), 0, "the sorted search read a value");
let nulled =
Members::of(&[Value::Varchar("MAIL".into()), Value::Null], false).expect("folds");
assert!(select_in(&sorted, &nulled, None).is_none());
}
#[test]
fn a_column_and_a_list_with_no_null_in_either_answer_every_row() {
let (column, flat, _) = filed(&["AIR", "MAIL", "SHIP"], vec![0, 1, 2, 1, 0]);
let list = [Value::Varchar("MAIL".into()), Value::Varchar("SHIP".into())];
for negated in [false, true] {
let members = Members::of(&list, negated).expect("this list folds");
for input in [&column, &flat] {
let answer =
in_set(input, &members, &LogicalType::Boolean).expect("the lookup runs");
assert_eq!(
answer.validity().live(),
rudb_vector::Live::All,
"a row came back null"
);
let read: Vec<Value> = (0..5).map(|row| answer.value_at(row)).collect();
let want =
[false, true, true, true, false].map(|hit| Value::Boolean(hit != negated));
assert_eq!(read, want);
}
}
}
#[test]
fn a_list_says_how_many_distinct_values_it_holds() {
let list = [Value::Integer(1), Value::Integer(1), Value::Integer(2), Value::Null];
let members = Members::of(&list, false).expect("this list folds");
assert_eq!(members.len(), 2);
assert!(!members.is_empty());
}
}