use std::cmp::Ordering;
use crate::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
Equal,
Less,
LessOrEqual,
Greater,
GreaterOrEqual,
}
impl Op {
#[must_use]
pub fn flipped(self) -> Self {
match self {
Self::Equal => Self::Equal,
Self::Less => Self::Greater,
Self::LessOrEqual => Self::GreaterOrEqual,
Self::Greater => Self::Less,
Self::GreaterOrEqual => Self::LessOrEqual,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Bound {
Int(i128),
Real(f64),
Bytes(Vec<u8>),
}
impl Bound {
#[must_use]
pub fn of_value(value: &Value) -> Option<Self> {
Some(match value {
Value::Boolean(flag) => Self::Int(i128::from(*flag)),
Value::TinyInt(number) => Self::Int(i128::from(*number)),
Value::SmallInt(number) => Self::Int(i128::from(*number)),
Value::Integer(number) => Self::Int(i128::from(*number)),
Value::BigInt(number) => Self::Int(i128::from(*number)),
Value::HugeInt(number) => Self::Int(*number),
Value::UTinyInt(number) => Self::Int(i128::from(*number)),
Value::USmallInt(number) => Self::Int(i128::from(*number)),
Value::UInteger(number) => Self::Int(i128::from(*number)),
Value::UBigInt(number) => Self::Int(i128::from(*number)),
Value::UHugeInt(number) => Self::Int(i128::try_from(*number).ok()?),
Value::Date(days) => Self::Int(i128::from(*days)),
Value::Float(number) => Self::Real(f64::from(*number)),
Value::Double(number) => Self::Real(*number),
Value::Varchar(text) => Self::Bytes(text.as_bytes().to_vec()),
Value::Blob(bytes) => Self::Bytes(bytes.clone()),
_ => return None,
})
}
#[must_use]
pub fn order(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::Int(left), Self::Int(right)) => Some(left.cmp(right)),
(Self::Real(left), Self::Real(right)) => left.partial_cmp(right),
(Self::Bytes(left), Self::Bytes(right)) => Some(left.as_slice().cmp(right)),
_ => None,
}
}
#[must_use]
pub fn smaller(self, other: Self) -> Self {
match self.order(&other) {
Some(Ordering::Greater) => other,
_ => self,
}
}
#[must_use]
pub fn larger(self, other: Self) -> Self {
match self.order(&other) {
Some(Ordering::Less) => other,
_ => self,
}
}
}
#[must_use]
pub fn excluded(op: Op, value: &Bound, low: Option<&Bound>, high: Option<&Bound>) -> bool {
match op {
Op::Less => holds(low, value, &[Ordering::Greater, Ordering::Equal]),
Op::LessOrEqual => holds(low, value, &[Ordering::Greater]),
Op::Greater => holds(high, value, &[Ordering::Less, Ordering::Equal]),
Op::GreaterOrEqual => holds(high, value, &[Ordering::Less]),
Op::Equal => {
holds(low, value, &[Ordering::Greater]) || holds(high, value, &[Ordering::Less])
}
}
}
fn holds(bound: Option<&Bound>, value: &Bound, wanted: &[Ordering]) -> bool {
bound.and_then(|bound| bound.order(value)).is_some_and(|order| wanted.contains(&order))
}
#[cfg(test)]
mod tests {
use super::{Bound, Op, excluded};
use crate::Value;
fn range() -> (Bound, Bound) {
(Bound::Int(10), Bound::Int(20))
}
#[test]
fn a_constant_below_the_range_rules_out_equality_and_nothing_else() {
let (low, high) = range();
let five = Bound::Int(5);
assert!(excluded(Op::Equal, &five, Some(&low), Some(&high)));
assert!(excluded(Op::Less, &five, Some(&low), Some(&high)), "nothing is below 5");
assert!(excluded(Op::LessOrEqual, &five, Some(&low), Some(&high)));
assert!(!excluded(Op::Greater, &five, Some(&low), Some(&high)), "everything is above 5");
assert!(!excluded(Op::GreaterOrEqual, &five, Some(&low), Some(&high)));
}
#[test]
fn a_constant_above_the_range_rules_out_the_other_direction() {
let (low, high) = range();
let fifty = Bound::Int(50);
assert!(excluded(Op::Equal, &fifty, Some(&low), Some(&high)));
assert!(!excluded(Op::Less, &fifty, Some(&low), Some(&high)));
assert!(excluded(Op::Greater, &fifty, Some(&low), Some(&high)));
assert!(excluded(Op::GreaterOrEqual, &fifty, Some(&low), Some(&high)));
}
#[test]
fn a_constant_at_either_end_of_the_range_is_kept() {
let (low, high) = range();
for value in [Bound::Int(10), Bound::Int(20)] {
assert!(!excluded(Op::Equal, &value, Some(&low), Some(&high)));
assert!(!excluded(Op::LessOrEqual, &value, Some(&low), Some(&high)));
assert!(!excluded(Op::GreaterOrEqual, &value, Some(&low), Some(&high)));
}
assert!(excluded(Op::Less, &Bound::Int(10), Some(&low), Some(&high)));
assert!(excluded(Op::Greater, &Bound::Int(20), Some(&low), Some(&high)));
}
#[test]
fn a_missing_bound_rules_nothing_out() {
let high = Bound::Int(20);
assert!(!excluded(Op::Less, &Bound::Int(5), None, Some(&high)));
assert!(!excluded(Op::Equal, &Bound::Int(5), None, None));
}
#[test]
fn a_bound_of_another_domain_rules_nothing_out() {
let (low, high) = range();
let text = Bound::Bytes(b"x".to_vec());
assert!(!excluded(Op::Equal, &text, Some(&low), Some(&high)));
assert!(!excluded(Op::Less, &text, Some(&low), Some(&high)));
}
#[test]
fn a_nan_bound_rules_nothing_out() {
let nan = Bound::Real(f64::NAN);
for op in [Op::Equal, Op::Less, Op::LessOrEqual, Op::Greater, Op::GreaterOrEqual] {
assert!(!excluded(op, &Bound::Real(1.0), Some(&nan), Some(&nan)));
}
}
#[test]
fn a_null_constant_has_no_bound() {
assert!(Bound::of_value(&Value::Null).is_none());
assert_eq!(Bound::of_value(&Value::Integer(7)), Some(Bound::Int(7)));
}
#[test]
fn a_timestamp_constant_makes_no_bound() {
assert_eq!(Bound::of_value(&Value::Timestamp(1)), None);
assert_eq!(Bound::of_value(&Value::Time(1)), None);
assert_eq!(Bound::of_value(&Value::Date(1)), Some(Bound::Int(1)));
}
#[test]
fn a_flipped_op_is_the_one_with_its_operands_the_other_way_round() {
assert_eq!(Op::Less.flipped(), Op::Greater);
assert_eq!(Op::GreaterOrEqual.flipped(), Op::LessOrEqual);
assert_eq!(Op::Equal.flipped(), Op::Equal);
}
#[test]
fn a_minimum_and_a_maximum_accumulate() {
let lower = Bound::Int(4).smaller(Bound::Int(9));
let upper = Bound::Int(4).larger(Bound::Int(9));
assert_eq!(lower, Bound::Int(4));
assert_eq!(upper, Bound::Int(9));
assert_eq!(Bound::Int(4).smaller(Bound::Bytes(Vec::new())), Bound::Int(4));
}
}