use std::cmp::Ordering;
use rudb_common::{Error, LogicalType, Result, Value};
use rudb_vector::{Data, Form, StringColumn, Validity, Vector};
use crate::fallback::{self, Kernel};
use crate::number::{approximate, integral};
use crate::shape::{first, identity, nulls_of, single};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Comparison {
Equal,
NotEqual,
Less,
LessOrEqual,
Greater,
GreaterOrEqual,
DistinctFrom,
NotDistinctFrom,
}
impl Comparison {
#[must_use]
pub fn is_total(self) -> bool {
matches!(self, Self::DistinctFrom | Self::NotDistinctFrom)
}
#[must_use]
pub fn swapped(self) -> Self {
match self {
Self::Less => Self::Greater,
Self::LessOrEqual => Self::GreaterOrEqual,
Self::Greater => Self::Less,
Self::GreaterOrEqual => Self::LessOrEqual,
same => same,
}
}
}
pub fn compare(op: Comparison, left: &Vector, right: &Vector) -> Result<Vector> {
if left.len() != right.len() {
return Err(Error::internal(format!(
"a comparison of a {} row vector with a {} row one",
left.len(),
right.len()
)));
}
let len = left.len();
if left.form() == Form::Constant && right.form() == Form::Constant && len > 0 {
let single = compare_values(op, &left.value_at(0), &right.value_at(0))?;
return Ok(Vector::constant(LogicalType::Boolean, single, len));
}
let (left_valid, right_valid) = (nulls_of(left), nulls_of(right));
if !op.is_total()
&& (left_valid == Validity::AllInvalid || right_valid == Validity::AllInvalid)
&& len > 0
{
return boolean(vec![false; len], Validity::AllInvalid, len);
}
if let Some(answers) = specialized(op, left, right, &left_valid, &right_valid) {
let validity =
if op.is_total() { Validity::AllValid } else { left_valid.and(&right_valid, len) };
return boolean(blank_the_nulls(answers, &validity), validity, len);
}
fallback::record(Kernel::Compare, left.form(), right.form());
let mut values = Vec::with_capacity(len);
for index in 0..len {
values.push(compare_values(op, &left.value_at(index), &right.value_at(index))?);
}
Vector::from_values(LogicalType::Boolean, &values)
}
fn boolean(answers: Vec<bool>, validity: Validity, len: usize) -> Result<Vector> {
let validity = if len == 0 { Validity::AllValid } else { validity.normalize(len) };
Ok(Vector::flat(LogicalType::Boolean, Data::Bool(answers))?.with_validity(validity))
}
fn blank_the_nulls(mut answers: Vec<bool>, validity: &Validity) -> Vec<bool> {
if let Validity::Mask(mask) = validity {
for (index, answer) in answers.iter_mut().enumerate() {
if !mask.get(index) {
*answer = false;
}
}
}
answers
}
fn specialized(
op: Comparison,
left: &Vector,
right: &Vector,
left_valid: &Validity,
right_valid: &Validity,
) -> Option<Vec<bool>> {
if left.logical_type() != right.logical_type() {
return None;
}
let len = left.len();
if let (Some(one), Some(other)) = (left.data(), right.data()) {
return dispatch(op, len, one, identity, other, identity, left_valid, right_valid);
}
if let (Some(one), Some(value)) = (left.data(), right.constant_value()) {
let held = single(left.logical_type(), value)?;
let other = held.data()?;
return dispatch(op, len, one, identity, other, first, left_valid, right_valid);
}
if let (Some(value), Some(other)) = (left.constant_value(), right.data()) {
let held = single(right.logical_type(), value)?;
let one = held.data()?;
return dispatch(op.swapped(), len, other, identity, one, first, right_valid, left_valid);
}
if let (Some((codes, values)), Some(value)) = (left.dictionary_parts(), right.constant_value())
{
let one = values.data()?;
let held = single(left.logical_type(), value)?;
let other = held.data()?;
let at = |index: usize| codes[index] as usize;
return dispatch(op, len, one, at, other, first, left_valid, right_valid);
}
if let (Some(value), Some((codes, values))) = (left.constant_value(), right.dictionary_parts())
{
let other = values.data()?;
let held = single(right.logical_type(), value)?;
let one = held.data()?;
let at = |index: usize| codes[index] as usize;
return dispatch(op.swapped(), len, other, at, one, first, right_valid, left_valid);
}
None
}
#[expect(
clippy::too_many_arguments,
reason = "two sides with an index each, the operator, the length and two validities, all of \
which the loop needs and none of which is worth a struct that exists for one call"
)]
fn dispatch<L, R>(
op: Comparison,
len: usize,
left: &Data,
at_left: L,
right: &Data,
at_right: R,
left_valid: &Validity,
right_valid: &Validity,
) -> Option<Vec<bool>>
where
L: Fn(usize) -> usize,
R: Fn(usize) -> usize,
{
macro_rules! layouts {
($($variant:ident),+ $(,)?) => {
match (left, right) {
$(
(Data::$variant(one), Data::$variant(other)) => Some(sweep(
op,
len,
|index| one[at_left(index)].cmp(&other[at_right(index)]),
left_valid,
right_valid,
)),
)+
(Data::Float32(one), Data::Float32(other)) => Some(sweep(
op,
len,
|index| {
float_order(
f64::from(one[at_left(index)]),
f64::from(other[at_right(index)]),
)
},
left_valid,
right_valid,
)),
(Data::Float64(one), Data::Float64(other)) => Some(sweep(
op,
len,
|index| float_order(one[at_left(index)], other[at_right(index)]),
left_valid,
right_valid,
)),
(Data::Varlen(one), Data::Varlen(other)) => Some(sweep(
op,
len,
|index| string_order(one, at_left(index), other, at_right(index)),
left_valid,
right_valid,
)),
_ => None,
}
};
}
layouts!(
Bool, Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128, Interval
)
}
fn string_order(
left: &StringColumn,
at_left: usize,
right: &StringColumn,
at_right: usize,
) -> Ordering {
let (Some(one), Some(other)) = (left.views().get(at_left), right.views().get(at_right)) else {
return Ordering::Equal;
};
let (prefix, against) = (one.prefix(), other.prefix());
if prefix != against {
return prefix.cmp(&against);
}
let bytes = left.bytes(at_left).unwrap_or_default();
let against_bytes = right.bytes(at_right).unwrap_or_default();
bytes.cmp(against_bytes)
}
fn sweep<O>(
op: Comparison,
len: usize,
order_at: O,
left_valid: &Validity,
right_valid: &Validity,
) -> Vec<bool>
where
O: Fn(usize) -> Ordering,
{
let mut answers = vec![false; len];
match op {
Comparison::Equal => fill(&mut answers, order_at, |o| o == Ordering::Equal),
Comparison::NotEqual => fill(&mut answers, order_at, |o| o != Ordering::Equal),
Comparison::Less => fill(&mut answers, order_at, |o| o == Ordering::Less),
Comparison::LessOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Greater),
Comparison::Greater => fill(&mut answers, order_at, |o| o == Ordering::Greater),
Comparison::GreaterOrEqual => fill(&mut answers, order_at, |o| o != Ordering::Less),
Comparison::DistinctFrom => {
total(&mut answers, order_at, left_valid, right_valid);
for answer in &mut answers {
*answer = !*answer;
}
}
Comparison::NotDistinctFrom => total(&mut answers, order_at, left_valid, right_valid),
}
answers
}
#[inline]
fn fill<O, H>(answers: &mut [bool], order_at: O, held: H)
where
O: Fn(usize) -> Ordering,
H: Fn(Ordering) -> bool,
{
for (index, answer) in answers.iter_mut().enumerate() {
*answer = held(order_at(index));
}
}
fn total<O>(answers: &mut [bool], order_at: O, left_valid: &Validity, right_valid: &Validity)
where
O: Fn(usize) -> Ordering,
{
if *left_valid == Validity::AllValid && *right_valid == Validity::AllValid {
fill(answers, order_at, |o| o == Ordering::Equal);
return;
}
for (index, answer) in answers.iter_mut().enumerate() {
*answer = match (left_valid.is_valid(index), right_valid.is_valid(index)) {
(true, true) => order_at(index) == Ordering::Equal,
(false, false) => true,
_ => false,
};
}
}
pub fn compare_values(op: Comparison, left: &Value, right: &Value) -> Result<Value> {
if op.is_total() {
let same = match (left.is_null(), right.is_null()) {
(true, true) => true,
(true, false) | (false, true) => false,
(false, false) => order(left, right)? == Ordering::Equal,
};
return Ok(Value::Boolean(match op {
Comparison::NotDistinctFrom => same,
_ => !same,
}));
}
if left.is_null() || right.is_null() {
return Ok(Value::Null);
}
let ordering = order(left, right)?;
let held = match op {
Comparison::Equal => ordering == Ordering::Equal,
Comparison::NotEqual => ordering != Ordering::Equal,
Comparison::Less => ordering == Ordering::Less,
Comparison::LessOrEqual => ordering != Ordering::Greater,
Comparison::Greater => ordering == Ordering::Greater,
Comparison::GreaterOrEqual => ordering != Ordering::Less,
Comparison::DistinctFrom | Comparison::NotDistinctFrom => {
return Err(Error::internal("a total comparison reached the ordered path"));
}
};
Ok(Value::Boolean(held))
}
pub fn order(left: &Value, right: &Value) -> Result<Ordering> {
match (left, right) {
(Value::Null, _) | (_, Value::Null) => {
Err(Error::internal("a null reached the ordering path"))
}
(Value::Boolean(a), Value::Boolean(b)) => Ok(a.cmp(b)),
(Value::Varchar(a), Value::Varchar(b)) => Ok(a.as_bytes().cmp(b.as_bytes())),
(Value::Blob(a), Value::Blob(b)) => Ok(a.cmp(b)),
(Value::Date(a), Value::Date(b)) => Ok(a.cmp(b)),
(Value::Time(a), Value::Time(b)) | (Value::Timestamp(a), Value::Timestamp(b)) => {
Ok(a.cmp(b))
}
(
Value::Interval { months: am, days: ad, micros: au },
Value::Interval { months: bm, days: bd, micros: bu },
) => Ok((am, ad, au).cmp(&(bm, bd, bu))),
_ => numeric_order(left, right),
}
}
fn numeric_order(left: &Value, right: &Value) -> Result<Ordering> {
if let (Some(a), Some(b)) = (integral(left), integral(right)) {
return Ok(a.cmp(&b));
}
if let (
Value::Decimal { unscaled: a, scale: sa, .. },
Value::Decimal { unscaled: b, scale: sb, .. },
) = (left, right)
{
if sa == sb {
return Ok(a.cmp(b));
}
}
match (approximate(left), approximate(right)) {
(Some(a), Some(b)) => Ok(float_order(a, b)),
_ => Err(Error::not_implemented(format!(
"comparing {} with {}",
left.logical_type(),
right.logical_type()
))),
}
}
fn float_order(left: f64, right: f64) -> Ordering {
if left == right {
return Ordering::Equal;
}
match (left.is_nan(), right.is_nan()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => left.partial_cmp(&right).unwrap_or(Ordering::Equal),
}
}
pub fn order_with_nulls(left: &Value, right: &Value, nulls_first: bool) -> Result<Ordering> {
match (left.is_null(), right.is_null()) {
(true, true) => Ok(Ordering::Equal),
(true, false) => Ok(if nulls_first { Ordering::Less } else { Ordering::Greater }),
(false, true) => Ok(if nulls_first { Ordering::Greater } else { Ordering::Less }),
(false, false) => order(left, right),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compared(op: Comparison, left: Value, right: Value) -> Value {
compare_values(op, &left, &right).expect("these types compare")
}
const EVERY: [Comparison; 8] = [
Comparison::Equal,
Comparison::NotEqual,
Comparison::Less,
Comparison::LessOrEqual,
Comparison::Greater,
Comparison::GreaterOrEqual,
Comparison::DistinctFrom,
Comparison::NotDistinctFrom,
];
fn oracle(op: Comparison, left: &Vector, right: &Vector) -> Vector {
let values: Vec<Value> = (0..left.len())
.map(|index| {
compare_values(op, &left.value_at(index), &right.value_at(index))
.expect("the oracle is only asked about types that compare")
})
.collect();
Vector::from_values(LogicalType::Boolean, &values).expect("booleans")
}
fn agrees(op: Comparison, left: &Vector, right: &Vector) {
let fast = compare(op, left, right).expect("compares");
let slow = oracle(op, left, right);
assert_eq!(fast, slow, "{op:?} on a {:?} against a {:?}", left.form(), right.form());
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn below(&mut self, bound: u64) -> u64 {
self.next() % bound
}
}
#[test]
fn an_ordinary_comparison_is_null_when_either_side_is() {
assert_eq!(compared(Comparison::Equal, Value::Integer(1), Value::Null), Value::Null);
assert_eq!(compared(Comparison::Less, Value::Null, Value::Integer(1)), Value::Null);
}
#[test]
fn a_total_comparison_is_never_null() {
assert_eq!(
compared(Comparison::NotDistinctFrom, Value::Null, Value::Null),
Value::Boolean(true)
);
assert_eq!(
compared(Comparison::NotDistinctFrom, Value::Integer(1), Value::Null),
Value::Boolean(false)
);
assert_eq!(
compared(Comparison::DistinctFrom, Value::Integer(1), Value::Null),
Value::Boolean(true)
);
}
#[test]
fn a_string_compares_by_bytes() {
assert_eq!(
compared(Comparison::Less, Value::Varchar("a".into()), Value::Varchar("b".into())),
Value::Boolean(true)
);
assert_eq!(
compared(Comparison::Less, Value::Varchar("Z".into()), Value::Varchar("a".into())),
Value::Boolean(true)
);
}
#[test]
fn two_nans_are_one_value_and_they_sort_above_the_numbers() {
assert_eq!(
compared(Comparison::Equal, Value::Double(f64::NAN), Value::Double(f64::NAN)),
Value::Boolean(true)
);
assert_eq!(
compared(Comparison::Greater, Value::Double(f64::NAN), Value::Double(1e300)),
Value::Boolean(true)
);
}
#[test]
fn zero_has_one_value_however_it_is_signed() {
assert_eq!(
compared(Comparison::Equal, Value::Double(0.0), Value::Double(-0.0)),
Value::Boolean(true)
);
}
#[test]
fn a_number_compares_the_same_however_it_is_stored() {
assert_eq!(
compared(Comparison::Equal, Value::Integer(3), Value::BigInt(3)),
Value::Boolean(true)
);
assert_eq!(
compared(Comparison::Less, Value::Integer(3), Value::Double(3.5)),
Value::Boolean(true)
);
}
#[test]
fn nulls_go_where_the_query_asked_for_them() {
assert_eq!(
order_with_nulls(&Value::Null, &Value::Integer(1), true).expect("orders"),
Ordering::Less
);
assert_eq!(
order_with_nulls(&Value::Null, &Value::Integer(1), false).expect("orders"),
Ordering::Greater
);
}
#[test]
fn two_constant_vectors_cost_one_comparison() {
let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 512);
let right = Vector::constant(LogicalType::Integer, Value::Integer(2), 512);
let result = compare(Comparison::Less, &left, &right).expect("compares");
assert_eq!(result.form(), Form::Constant);
assert_eq!(result.value_at(500), Value::Boolean(true));
}
#[test]
fn a_comparison_of_two_vectors_is_one_answer_per_row() {
let left = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Integer(5), Value::Null],
)
.expect("three rows");
let right = Vector::constant(LogicalType::Integer, Value::Integer(3), 3);
let result = compare(Comparison::Greater, &left, &right).expect("compares");
assert_eq!(result.value_at(0), Value::Boolean(false));
assert_eq!(result.value_at(1), Value::Boolean(true));
assert_eq!(result.value_at(2), Value::Null);
}
#[test]
fn two_vectors_of_different_lengths_are_caught() {
let left = Vector::constant(LogicalType::Integer, Value::Integer(1), 4);
let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 5);
let error = compare(Comparison::Equal, &left, &right).expect_err("ragged");
assert!(error.message().contains("4 row vector"), "{error}");
}
#[test]
fn turning_a_comparison_around_is_what_the_other_side_would_have_said() {
for op in EVERY {
let left = Value::Integer(3);
let right = Value::Integer(7);
assert_eq!(
compare_values(op, &left, &right).expect("compares"),
compare_values(op.swapped(), &right, &left).expect("compares"),
"{op:?}"
);
}
}
#[test]
fn every_specialized_path_agrees_with_the_row_at_a_time_path() {
let mut rng = Rng(0x5eed_1234_9876_4321);
let types: [LogicalType; 10] = [
LogicalType::Boolean,
LogicalType::TinyInt,
LogicalType::SmallInt,
LogicalType::Integer,
LogicalType::BigInt,
LogicalType::HugeInt,
LogicalType::UInteger,
LogicalType::Float,
LogicalType::Double,
LogicalType::Varchar,
];
for ty in &types {
for nulls in [0u64, 1, 3] {
let len = 37;
let make = |rng: &mut Rng| {
let values: Vec<Value> = (0..len)
.map(|_| {
if nulls > 0 && rng.below(nulls + 1) == 0 {
Value::Null
} else {
sample(ty, rng)
}
})
.collect();
Vector::from_values(ty.clone(), &values).expect("a flat vector")
};
let left = make(&mut rng);
let right = make(&mut rng);
let literal = sample(ty, &mut rng);
let constant = Vector::constant(ty.clone(), literal, len);
let null_constant = Vector::constant(ty.clone(), Value::Null, len);
let codes: Vec<u32> =
(0..len).map(|_| rng.below(left.len() as u64) as u32).collect();
let dictionary =
Vector::dictionary(codes, left.clone()).expect("codes are in range");
for op in EVERY {
agrees(op, &left, &right);
agrees(op, &left, &constant);
agrees(op, &constant, &left);
agrees(op, &left, &null_constant);
agrees(op, &null_constant, &left);
agrees(op, &dictionary, &constant);
agrees(op, &constant, &dictionary);
}
}
}
}
fn sample(ty: &LogicalType, rng: &mut Rng) -> Value {
match ty {
LogicalType::Boolean => Value::Boolean(rng.below(2) == 1),
LogicalType::TinyInt => Value::TinyInt(rng.below(7) as i8 - 3),
LogicalType::SmallInt => Value::SmallInt(rng.below(11) as i16 - 5),
LogicalType::Integer => Value::Integer(rng.below(9) as i32 - 4),
LogicalType::BigInt => Value::BigInt(rng.below(9) as i64 - 4),
LogicalType::HugeInt => Value::HugeInt(i128::from(rng.below(9)) - 4),
LogicalType::UInteger => Value::UInteger(rng.below(9) as u32),
LogicalType::Float => Value::Float(match rng.below(5) {
0 => f32::NAN,
1 => -0.0,
other => other as f32 - 2.0,
}),
LogicalType::Double => Value::Double(match rng.below(5) {
0 => f64::NAN,
1 => -0.0,
other => other as f64 - 2.0,
}),
LogicalType::Varchar => Value::Varchar(
match rng.below(6) {
0 => "",
1 => "ab",
2 => "abc",
3 => "abcdefghijkl",
4 => "abcdefghijklm",
_ => "abcdefghijklmnopqrstuvwxyz",
}
.to_owned(),
),
other => panic!("the generator has no values for {other}"),
}
}
#[test]
fn prefix_order_is_byte_order_whenever_the_prefixes_differ() {
let words =
["", "a", "ab", "abc", "abcd", "abcde", "b", "abcdefghijklmnop", "abcdefghijklmnoq"];
let mut column = StringColumn::new();
for word in words {
column.push(word);
}
for (i, one) in words.iter().enumerate() {
for (j, other) in words.iter().enumerate() {
assert_eq!(
string_order(&column, i, &column, j),
one.as_bytes().cmp(other.as_bytes()),
"{one:?} against {other:?}"
);
}
}
}
#[test]
fn a_dictionary_against_a_constant_reads_its_nulls_from_the_values() {
let values = Vector::from_values(
LogicalType::Integer,
&[Value::Integer(1), Value::Null, Value::Integer(9)],
)
.expect("three values");
let dictionary =
Vector::dictionary(vec![0, 1, 2, 1, 0], values).expect("codes are in range");
let constant = Vector::constant(LogicalType::Integer, Value::Integer(5), 5);
let result = compare(Comparison::Less, &dictionary, &constant).expect("compares");
assert_eq!(result.value_at(0), Value::Boolean(true));
assert_eq!(result.value_at(1), Value::Null);
assert_eq!(result.value_at(2), Value::Boolean(false));
assert_eq!(result.value_at(3), Value::Null);
assert_eq!(result.value_at(4), Value::Boolean(true));
}
#[test]
fn a_form_pair_with_no_loop_is_still_right_and_says_so() {
let _turn = fallback::TURN.lock().expect("no test panics while holding this");
let before = fallback::count(Kernel::Compare, Form::Sequence, Form::Flat);
let sequence = Vector::sequence(10, 1, 4);
let flat = Vector::from_values(
LogicalType::BigInt,
&[Value::BigInt(9), Value::BigInt(11), Value::BigInt(12), Value::Null],
)
.expect("four rows");
let result = compare(Comparison::Less, &sequence, &flat).expect("compares");
assert_eq!(result.value_at(0), Value::Boolean(false));
assert_eq!(result.value_at(1), Value::Boolean(false));
assert_eq!(result.value_at(2), Value::Boolean(false));
assert_eq!(result.value_at(3), Value::Null);
assert!(fallback::count(Kernel::Compare, Form::Sequence, Form::Flat) > before);
}
#[test]
fn a_side_that_is_entirely_null_answers_without_reading_the_other() {
let nulls = Vector::constant(LogicalType::Integer, Value::Null, 6);
let flat = Vector::from_values(
LogicalType::Integer,
&[
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
Value::Integer(4),
Value::Integer(5),
Value::Integer(6),
],
)
.expect("six rows");
agrees(Comparison::Less, &nulls, &flat);
agrees(Comparison::Equal, &flat, &nulls);
assert_eq!(
compare(Comparison::Less, &nulls, &flat).expect("compares").validity(),
&Validity::AllInvalid
);
}
#[test]
fn an_empty_comparison_is_an_empty_answer() {
let left = Vector::from_values(LogicalType::Integer, &[]).expect("no rows");
let right = Vector::constant(LogicalType::Integer, Value::Integer(1), 0);
let result = compare(Comparison::Equal, &left, &right).expect("compares");
assert_eq!(result.len(), 0);
}
}