-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
575 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "vortex-expr" | ||
version = { workspace = true } | ||
description = "Vortex Expressions" | ||
homepage = { workspace = true } | ||
repository = { workspace = true } | ||
authors = { workspace = true } | ||
license = { workspace = true } | ||
keywords = { workspace = true } | ||
include = { workspace = true } | ||
edition = { workspace = true } | ||
rust-version = { workspace = true } | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
vortex-dtype = { path = "../vortex-dtype" } | ||
vortex-error = { path = "../vortex-error" } | ||
vortex-scalar = { path = "../vortex-scalar" } | ||
|
||
|
||
[dev-dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Vortex Expressions | ||
|
||
Expressions for querying vortex arrays. The query algebra is designed to express a minimal | ||
superset of linear operations that can be pushed down to vortex metadata. Conversely, not all | ||
operations that can be expressed in this algebra can be pushed down to metadata. | ||
|
||
Takes inspiration from postgres https://www.postgresql.org/docs/current/sql-expressions.html | ||
and datafusion https://github.com/apache/datafusion/tree/5fac581efbaffd0e6a9edf931182517524526afd/datafusion/expr |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
use core::fmt; | ||
use std::fmt::{Display, Formatter}; | ||
|
||
use vortex_dtype::{match_each_native_ptype, DType}; | ||
use vortex_scalar::{BoolScalar, PrimitiveScalar}; | ||
|
||
use crate::expressions::{BinaryExpr, Expr}; | ||
use crate::operators::Operator; | ||
use crate::scalar::ScalarDisplayWrapper; | ||
|
||
impl Display for Expr { | ||
fn fmt(&self, f: &mut Formatter) -> fmt::Result { | ||
match self { | ||
Expr::BinaryExpr(expr) => write!(f, "{expr}"), | ||
Expr::Field(d) => write!(f, "{d}"), | ||
Expr::Literal(v) => { | ||
let wrapped = ScalarDisplayWrapper(v); | ||
write!(f, "{wrapped}") | ||
} | ||
Expr::Not(expr) => write!(f, "NOT {expr}"), | ||
Expr::Minus(expr) => write!(f, "(- {expr})"), | ||
Expr::IsNull(expr) => write!(f, "{expr} IS NULL"), | ||
} | ||
} | ||
} | ||
|
||
impl Display for BinaryExpr { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
fn write_inner( | ||
f: &mut Formatter<'_>, | ||
outer: &Expr, | ||
outer_op_precedence: u8, | ||
) -> fmt::Result { | ||
if let Expr::BinaryExpr(inner) = outer { | ||
let inner_op_precedence = inner.op.precedence(); | ||
// if the child operator has lower precedence than the outer expression, | ||
// wrap it in parentheses to prevent inversion of priority | ||
if inner_op_precedence == 0 || inner_op_precedence < outer_op_precedence { | ||
write!(f, "({inner})")?; | ||
} else { | ||
write!(f, "{inner}")?; | ||
} | ||
} else if let Expr::Literal(scalar) = outer { | ||
// use alternative formatting for scalars | ||
let wrapped = ScalarDisplayWrapper(scalar); | ||
write!(f, "{wrapped}")?; | ||
} else { | ||
write!(f, "{outer}")?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
write_inner(f, self.left.as_ref(), self.op.precedence())?; | ||
write!(f, " {} ", self.op)?; | ||
write_inner(f, self.right.as_ref(), self.op.precedence()) | ||
} | ||
} | ||
|
||
/// Alternative display for scalars | ||
impl Display for ScalarDisplayWrapper<'_> { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
match self.0.dtype() { | ||
DType::Null => write!(f, "null"), | ||
DType::Bool(_) => match BoolScalar::try_from(self.0).expect("bool").value() { | ||
None => write!(f, "null"), | ||
Some(b) => write!(f, "{}", b), | ||
}, | ||
DType::Primitive(ptype, _) => match_each_native_ptype!(ptype, |$T| { | ||
match PrimitiveScalar::try_from(self.0).expect("primitive").typed_value::<$T>() { | ||
None => write!(f, "null"), | ||
Some(v) => write!(f, "{}{}", v, std::any::type_name::<$T>()), | ||
} | ||
}), | ||
DType::Utf8(_) => todo!(), | ||
DType::Binary(_) => todo!(), | ||
DType::Struct(..) => todo!(), | ||
DType::List(..) => todo!(), | ||
DType::Extension(..) => todo!(), | ||
} | ||
} | ||
} | ||
|
||
impl Display for Operator { | ||
fn fmt(&self, f: &mut Formatter) -> fmt::Result { | ||
let display = match &self { | ||
Operator::And => "AND", | ||
Operator::Or => "OR", | ||
Operator::EqualTo => "=", | ||
Operator::NotEqualTo => "!=", | ||
Operator::GreaterThan => ">", | ||
Operator::GreaterThanOrEqualTo => ">=", | ||
Operator::LessThan => "<", | ||
Operator::LessThanOrEqualTo => "<=", | ||
Operator::Plus => "+", | ||
Operator::Minus | Operator::UnaryMinus => "-", | ||
Operator::Multiplication => "*", | ||
Operator::Division => "/", | ||
Operator::Modulo => "%", | ||
}; | ||
write!(f, "{display}") | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::literal::lit; | ||
|
||
#[test] | ||
fn test_formatting() { | ||
// Addition | ||
assert_eq!(format!("{}", lit(1u32) + lit(2u32)), "1u32 + 2u32"); | ||
// Subtraction | ||
assert_eq!(format!("{}", lit(1u32) - lit(2u32)), "1u32 - 2u32"); | ||
// Multiplication | ||
assert_eq!(format!("{}", lit(1u32) * lit(2u32)), "1u32 * 2u32"); | ||
// Division | ||
assert_eq!(format!("{}", lit(1u32) / lit(2u32)), "1u32 / 2u32"); | ||
// Modulus | ||
assert_eq!(format!("{}", lit(1u32) % lit(2u32)), "1u32 % 2u32"); | ||
// Negate | ||
assert_eq!(format!("{}", -lit(1u32)), "(- 1u32)"); | ||
|
||
// And | ||
let string = format!("{}", lit(true).and(lit(false))); | ||
assert_eq!(string, "true AND false"); | ||
// Or | ||
let string = format!("{}", lit(true).or(lit(false))); | ||
assert_eq!(string, "true OR false"); | ||
// Not | ||
let string = format!("{}", !lit(1u32)); | ||
assert_eq!(string, "NOT 1u32"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use vortex_dtype::FieldName; | ||
|
||
use crate::expressions::BinaryExpr; | ||
use crate::expressions::Expr; | ||
use crate::operators::Operator; | ||
|
||
pub fn binary_expr(left: Expr, op: Operator, right: Expr) -> Expr { | ||
Expr::BinaryExpr(BinaryExpr::new(Box::new(left), op, Box::new(right))) | ||
} | ||
|
||
#[allow(dead_code)] | ||
/// Create a field expression based on a qualified field name. | ||
pub fn field(field: impl Into<FieldName>) -> Expr { | ||
Expr::Field(field.into()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use vortex_dtype::FieldName; | ||
use vortex_error::{vortex_bail, VortexResult}; | ||
use vortex_scalar::Scalar; | ||
|
||
use crate::expression_fns::binary_expr; | ||
use crate::operators::Operator; | ||
|
||
#[derive(Clone, Debug, PartialEq)] | ||
pub enum Expr { | ||
/// A binary expression such as "duration_seconds == 100" | ||
BinaryExpr(BinaryExpr), | ||
|
||
/// A named reference to a qualified field in a dtype. | ||
Field(FieldName), | ||
|
||
/// True if argument is NULL, false otherwise. This expression itself is never NULL. | ||
IsNull(Box<Expr>), | ||
|
||
/// A constant scalar value. | ||
Literal(Scalar), | ||
|
||
/// Additive inverse of an expression. The expression's type must be numeric. | ||
Minus(Box<Expr>), | ||
|
||
/// Negation of an expression. The expression's type must be a boolean. | ||
Not(Box<Expr>), | ||
} | ||
|
||
impl Expr { | ||
// binary logic | ||
|
||
pub fn and(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::And, other) | ||
} | ||
|
||
pub fn or(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::Or, other) | ||
} | ||
|
||
// comparisons | ||
|
||
pub fn eq(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::EqualTo, other) | ||
} | ||
|
||
pub fn not_eq(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::NotEqualTo, other) | ||
} | ||
|
||
pub fn gt(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::GreaterThan, other) | ||
} | ||
|
||
pub fn gt_eq(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::GreaterThanOrEqualTo, other) | ||
} | ||
|
||
pub fn lt(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::LessThan, other) | ||
} | ||
|
||
pub fn lt_eq(self, other: Expr) -> Expr { | ||
binary_expr(self, Operator::LessThanOrEqualTo, other) | ||
} | ||
|
||
// misc | ||
pub fn is_null(self) -> Expr { | ||
Expr::IsNull(Box::new(self)) | ||
} | ||
|
||
pub fn minus(&self) -> VortexResult<Self> { | ||
Ok(match self { | ||
Expr::Literal(scalar) => Expr::Literal(scalar.invert()?), | ||
_ => { | ||
vortex_bail!("Can only negate numeric literals") | ||
} | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, PartialEq)] | ||
pub struct BinaryExpr { | ||
pub left: Box<Expr>, | ||
pub op: Operator, | ||
pub right: Box<Expr>, | ||
} | ||
|
||
impl BinaryExpr { | ||
pub fn new(left: Box<Expr>, op: Operator, right: Box<Expr>) -> Self { | ||
Self { left, op, right } | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use crate::expression_fns::field; | ||
use crate::literal::lit; | ||
|
||
#[test] | ||
fn test_lit() { | ||
let exp = field("id").eq(lit(1)); | ||
let exp = exp.eq(lit(2).minus().unwrap()); | ||
let s = format!("{}", exp); | ||
assert_eq!(s, "id = Scalar { dtype: Primitive(I32, NonNullable), \ | ||
value: Primitive(I32(1)) } = Scalar { dtype: Primitive(I32, NonNullable), value: Primitive(I32(-2)) }") | ||
} | ||
|
||
#[test] | ||
fn test_negative() { | ||
let scalar: Scalar = 1.into(); | ||
let rhs: Expr = lit(scalar); | ||
field("id").eq(rhs); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#[cfg(test)] | ||
mod test { | ||
use crate::expression_fns::field; | ||
|
||
#[test] | ||
fn test_cant_negate_field() { | ||
field("id").minus().expect_err("cannot negate field"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
extern crate core; | ||
|
||
mod display; | ||
mod expression_fns; | ||
pub mod expressions; | ||
mod fields; | ||
mod literal; | ||
pub mod operators; | ||
pub mod scalar; |
Oops, something went wrong.