use datafusion::arrow::datatypes::DataType;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Comparison {
Eq,
Lt,
LtEq,
Gt,
GtEq,
}
impl Comparison {
pub const fn symbol(self) -> &'static str {
match self {
Comparison::Eq => "=",
Comparison::Lt => "<",
Comparison::LtEq => "<=",
Comparison::Gt => ">",
Comparison::GtEq => ">=",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Literal {
Int64(i64),
Float64(f64),
}
impl Literal {
pub const fn data_type(&self) -> DataType {
match self {
Literal::Int64(_) => DataType::Int64,
Literal::Float64(_) => DataType::Float64,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Predicate {
Compare {
column: usize,
op: Comparison,
literal: Literal,
},
And(Box<Predicate>, Box<Predicate>),
}
impl Predicate {
pub const fn compare(column: usize, op: Comparison, literal: Literal) -> Self {
Self::Compare {
column,
op,
literal,
}
}
pub fn and(left: Predicate, right: Predicate) -> Self {
Self::And(Box::new(left), Box::new(right))
}
pub fn columns(&self) -> Vec<usize> {
let mut out = Vec::new();
self.collect_columns(&mut out);
out
}
fn collect_columns(&self, out: &mut Vec<usize>) {
match self {
Predicate::Compare { column, .. } => out.push(*column),
Predicate::And(l, r) => {
l.collect_columns(out);
r.collect_columns(out);
}
}
}
pub fn leaf_count(&self) -> usize {
match self {
Predicate::Compare { .. } => 1,
Predicate::And(l, r) => l.leaf_count() + r.leaf_count(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AggregateFunction {
Sum,
Count,
Min,
Max,
}
impl AggregateFunction {
pub const fn name(self) -> &'static str {
match self {
AggregateFunction::Sum => "SUM",
AggregateFunction::Count => "COUNT",
AggregateFunction::Min => "MIN",
AggregateFunction::Max => "MAX",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AggregateSpec {
pub group_by: usize,
pub aggregates: Vec<(AggregateFunction, usize)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DistanceMetric {
L2,
Cosine,
}
impl DistanceMetric {
pub const fn name(self) -> &'static str {
match self {
DistanceMetric::L2 => "l2",
DistanceMetric::Cosine => "cosine",
}
}
}
pub const fn gpu_eligible_scalar(dt: &DataType) -> bool {
matches!(dt, DataType::Int64 | DataType::Float64)
}
pub fn vector_dimension(dt: &DataType) -> Option<usize> {
match dt {
DataType::FixedSizeList(field, n) if *field.data_type() == DataType::Float32 && *n > 0 => {
usize::try_from(*n).ok()
}
_ => None,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use std::sync::Arc;
use datafusion::arrow::datatypes::Field;
use super::*;
#[test]
fn predicate_collects_columns_in_order() {
let p = Predicate::and(
Predicate::compare(2, Comparison::Gt, Literal::Int64(1)),
Predicate::and(
Predicate::compare(0, Comparison::LtEq, Literal::Float64(0.5)),
Predicate::compare(2, Comparison::Eq, Literal::Int64(9)),
),
);
assert_eq!(p.columns(), vec![2, 0, 2]);
assert_eq!(p.leaf_count(), 3);
}
#[test]
fn coverage_rules() {
assert!(gpu_eligible_scalar(&DataType::Int64));
assert!(gpu_eligible_scalar(&DataType::Float64));
assert!(!gpu_eligible_scalar(&DataType::Int32));
assert!(!gpu_eligible_scalar(&DataType::Utf8));
let vec3 =
DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Float32, false)), 3);
assert_eq!(vector_dimension(&vec3), Some(3));
let f64s =
DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Float64, false)), 3);
assert_eq!(vector_dimension(&f64s), None);
assert_eq!(vector_dimension(&DataType::Int64), None);
}
#[test]
fn names_are_stable_for_explain_output() {
assert_eq!(AggregateFunction::Sum.name(), "SUM");
assert_eq!(AggregateFunction::Count.name(), "COUNT");
assert_eq!(DistanceMetric::Cosine.name(), "cosine");
assert_eq!(Comparison::GtEq.symbol(), ">=");
assert_eq!(Literal::Float64(1.5).data_type(), DataType::Float64);
}
}