use rudb_common::bounds::{Bound, Op, excluded};
use rudb_vector::{Chunk, Data, Form, Vector};
#[cfg(doc)]
use crate::MemoryTable;
#[derive(Debug, Clone)]
pub struct Probe {
pub column: usize,
pub op: Op,
pub value: Bound,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Range {
pub low: Option<Bound>,
pub high: Option<Bound>,
pub nulls: usize,
}
impl Range {
#[must_use]
pub fn excludes(&self, op: Op, value: &Bound) -> bool {
excluded(op, value, self.low.as_ref(), self.high.as_ref())
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Zone {
columns: Vec<Range>,
}
impl Zone {
#[must_use]
pub fn of(chunk: &Chunk) -> Self {
Self { columns: chunk.columns().iter().map(range).collect() }
}
#[must_use]
pub fn column(&self, index: usize) -> Option<&Range> {
self.columns.get(index)
}
#[must_use]
pub fn width(&self) -> usize {
self.columns.len()
}
#[must_use]
pub fn skips(&self, probes: &[Probe]) -> bool {
probes.iter().any(|probe| {
self.columns
.get(probe.column)
.is_some_and(|range| range.excludes(probe.op, &probe.value))
})
}
}
fn range(vector: &Vector) -> Range {
let nulls = vector.len() - vector.validity().count_valid(vector.len());
let (low, high) = match vector.form() {
Form::Constant => match vector.constant_value().and_then(Bound::of_value) {
Some(only) => (Some(only.clone()), Some(only)),
None => (None, None),
},
Form::Sequence => match vector.sequence_parts() {
Some((start, step)) => ends(start, step, vector.len()),
None => (None, None),
},
Form::BitPacked => match vector.packed_parts() {
Some(packed) => (Some(Bound::Int(packed.base())), Some(Bound::Int(packed.ceiling()))),
None => (None, None),
},
Form::Dictionary => match vector.dictionary_parts() {
Some((_, values)) => narrower(vector, values),
None => (None, None),
},
Form::Rle => match vector.run_parts() {
Some((_, values)) => narrower(vector, values),
None => (None, None),
},
Form::Flat => match vector.data() {
Some(data) => flat(vector, data),
None => (None, None),
},
Form::StringView | Form::Fsst => text(vector),
_ => (None, None),
};
Range { low, high, nulls }
}
fn narrower(vector: &Vector, values: &Vector) -> (Option<Bound>, Option<Bound>) {
let strings = matches!(values.data(), Some(Data::Varlen(_))) || values.text_parts().is_some();
if strings && values.len() > vector.len() {
return text(vector);
}
let inner = range(values);
(inner.low, inner.high)
}
fn ends(start: i64, step: i64, len: usize) -> (Option<Bound>, Option<Bound>) {
if len == 0 {
return (None, None);
}
let last = i128::from(start) + i128::from(step) * (len as i128 - 1);
let first = i128::from(start);
let (low, high) = if first <= last { (first, last) } else { (last, first) };
(Some(Bound::Int(low)), Some(Bound::Int(high)))
}
fn flat(vector: &Vector, data: &Data) -> (Option<Bound>, Option<Bound>) {
macro_rules! sweep {
($values:expr, $into:expr) => {{
let (low, high) = extremes($values, vector);
(low.and_then($into), high.and_then($into))
}};
}
let int = |number: i128| Some(Bound::Int(number));
match data {
Data::Bool(values) => sweep!(values, |flag: bool| int(i128::from(flag))),
Data::Int8(values) => sweep!(values, |n: i8| int(i128::from(n))),
Data::Int16(values) => sweep!(values, |n: i16| int(i128::from(n))),
Data::Int32(values) => sweep!(values, |n: i32| int(i128::from(n))),
Data::Int64(values) => sweep!(values, |n: i64| int(i128::from(n))),
Data::Int128(values) => sweep!(values, int),
Data::UInt8(values) => sweep!(values, |n: u8| int(i128::from(n))),
Data::UInt16(values) => sweep!(values, |n: u16| int(i128::from(n))),
Data::UInt32(values) => sweep!(values, |n: u32| int(i128::from(n))),
Data::UInt64(values) => sweep!(values, |n: u64| int(i128::from(n))),
Data::UInt128(values) => sweep!(values, |n: u128| i128::try_from(n).ok().and_then(int)),
Data::Float32(values) => sweep!(values, |n: f32| Some(Bound::Real(f64::from(n)))),
Data::Float64(values) => sweep!(values, |n: f64| Some(Bound::Real(n))),
Data::Varlen(_) => text(vector),
Data::Interval(_) | Data::Empty | _ => (None, None),
}
}
fn extremes<T: Copy + PartialOrd>(values: &[T], vector: &Vector) -> (Option<T>, Option<T>) {
let mut low: Option<T> = None;
let mut high: Option<T> = None;
if vector.validity().has_nulls(vector.len()) {
for (index, &value) in values.iter().enumerate() {
if !vector.is_null_at(index) {
widen(value, &mut low, &mut high);
}
}
} else {
for &value in values {
widen(value, &mut low, &mut high);
}
}
(low, high)
}
#[inline]
fn widen<T: Copy + PartialOrd>(value: T, low: &mut Option<T>, high: &mut Option<T>) {
if value.partial_cmp(&value).is_none() {
return;
}
if low.is_none_or(|held| value < held) {
*low = Some(value);
}
if high.is_none_or(|held| value > held) {
*high = Some(value);
}
}
fn text(vector: &Vector) -> (Option<Bound>, Option<Bound>) {
let mut low: Option<&[u8]> = None;
let mut high: Option<&[u8]> = None;
for index in 0..vector.len() {
let Some(bytes) = vector.bytes_at(index) else { continue };
if low.is_none_or(|held| bytes < held) {
low = Some(bytes);
}
if high.is_none_or(|held| bytes > held) {
high = Some(bytes);
}
}
(low.map(|bytes| Bound::Bytes(bytes.to_vec())), high.map(|bytes| Bound::Bytes(bytes.to_vec())))
}
#[cfg(test)]
mod tests {
use rudb_common::{LogicalType, Value};
use rudb_vector::{Chunk, Vector};
use super::{Bound, Op, Probe, Zone};
fn chunk(values: &[i32]) -> Chunk {
let held: Vec<Value> = values.iter().map(|n| Value::Integer(*n)).collect();
let vector = Vector::from_values(LogicalType::Integer, &held).expect("a column");
Chunk::new(vec![vector]).expect("a chunk")
}
#[test]
fn a_chunk_of_integers_knows_its_two_ends() {
let zone = Zone::of(&chunk(&[7, 2, 9, 4]));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Int(2)));
assert_eq!(range.high, Some(Bound::Int(9)));
assert_eq!(range.nulls, 0);
}
#[test]
fn a_probe_outside_the_range_skips_the_chunk_and_one_inside_it_does_not() {
let zone = Zone::of(&chunk(&[10, 20]));
let outside = vec![Probe { column: 0, op: Op::Equal, value: Bound::Int(62) }];
let inside = vec![Probe { column: 0, op: Op::Equal, value: Bound::Int(10) }];
assert!(zone.skips(&outside));
assert!(!zone.skips(&inside));
}
#[test]
fn one_probe_of_several_is_enough_to_skip() {
let zone = Zone::of(&chunk(&[10, 20]));
let probes = vec![
Probe { column: 0, op: Op::GreaterOrEqual, value: Bound::Int(10) },
Probe { column: 0, op: Op::Greater, value: Bound::Int(99) },
];
assert!(zone.skips(&probes));
}
#[test]
fn a_probe_on_a_column_the_zone_does_not_describe_keeps_the_chunk() {
let zone = Zone::of(&chunk(&[1]));
let probes = vec![Probe { column: 4, op: Op::Equal, value: Bound::Int(62) }];
assert!(!zone.skips(&probes));
}
#[test]
fn nulls_are_counted_and_do_not_move_the_ends() {
let values = vec![Value::Integer(5), Value::Null, Value::Integer(3)];
let vector = Vector::from_values(LogicalType::Integer, &values).expect("a column");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.nulls, 1);
assert_eq!(range.low, Some(Bound::Int(3)));
assert_eq!(range.high, Some(Bound::Int(5)));
}
#[test]
fn a_column_of_only_nulls_has_no_ends_and_rules_nothing_out() {
let values = vec![Value::Null, Value::Null];
let vector = Vector::from_values(LogicalType::Integer, &values).expect("a column");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.nulls, 2);
assert_eq!(range.low, None);
let probes = vec![Probe { column: 0, op: Op::Equal, value: Bound::Int(1) }];
assert!(!zone.skips(&probes));
}
#[test]
fn a_column_of_strings_is_ordered_as_bytes() {
let values = vec![
Value::Varchar("grace".to_string()),
Value::Varchar("ada".to_string()),
Value::Varchar("turing".to_string()),
];
let vector = Vector::from_values(LogicalType::Varchar, &values).expect("a column");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Bytes(b"ada".to_vec())));
assert_eq!(range.high, Some(Bound::Bytes(b"turing".to_vec())));
}
#[test]
fn a_dictionary_wider_than_its_chunk_is_read_through_its_codes() {
let values: Vec<Value> = ["ada", "babbage", "grace", "hopper", "turing"]
.iter()
.map(|s| s.to_string())
.map(Value::Varchar)
.collect();
let inner = Vector::from_values(LogicalType::Varchar, &values).expect("a dictionary");
let vector = Vector::dictionary(vec![1, 2], inner).expect("two rows of five values");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Bytes(b"babbage".to_vec())), "not ada");
assert_eq!(range.high, Some(Bound::Bytes(b"grace".to_vec())), "not turing");
let probes = vec![Probe { column: 0, op: Op::Equal, value: Bound::Bytes(b"ada".to_vec()) }];
assert!(zone.skips(&probes));
}
#[test]
fn a_dictionary_takes_its_bounds_from_its_values_and_not_from_its_codes() {
let values = vec![Value::Integer(1), Value::Integer(50), Value::Integer(99)];
let inner = Vector::from_values(LogicalType::Integer, &values).expect("a dictionary");
let vector = Vector::dictionary(vec![1, 1, 1], inner).expect("a coded column");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Int(1)), "every row is 50, and the bound is wider");
assert_eq!(range.high, Some(Bound::Int(99)));
let probes = vec![Probe { column: 0, op: Op::Equal, value: Bound::Int(1) }];
assert!(!zone.skips(&probes));
}
#[test]
fn a_constant_column_is_both_ends_of_itself() {
let vector = Vector::constant(LogicalType::Integer, Value::Integer(62), 100);
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Int(62)));
assert_eq!(range.high, Some(Bound::Int(62)));
let probes = vec![Probe { column: 0, op: Op::Equal, value: Bound::Int(63) }];
assert!(zone.skips(&probes));
}
#[test]
fn a_nan_in_a_column_does_not_take_the_ends_with_it() {
let values = vec![
Value::Double(2.5),
Value::Double(f64::NAN),
Value::Double(9.0),
Value::Double(1.0),
];
let vector = Vector::from_values(LogicalType::Double, &values).expect("a column");
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Real(1.0)));
assert_eq!(range.high, Some(Bound::Real(9.0)));
let probes = vec![Probe { column: 0, op: Op::Greater, value: Bound::Real(9.0) }];
assert!(zone.skips(&probes));
}
#[test]
fn a_sequence_is_its_first_and_last_value() {
let vector = Vector::sequence(100, -5, 4);
let zone = Zone::of(&Chunk::new(vec![vector]).expect("a chunk"));
let range = zone.column(0).expect("one column");
assert_eq!(range.low, Some(Bound::Int(85)), "it counts down");
assert_eq!(range.high, Some(Bound::Int(100)));
}
}