use crate::select::Select;
use crate::value::Value;
pub const VECTOR_L2_DISTANCE_FUNCTION: &str = "__nautilus_vector_l2_distance";
pub const VECTOR_INNER_PRODUCT_FUNCTION: &str = "__nautilus_vector_inner_product";
pub const VECTOR_COSINE_DISTANCE_FUNCTION: &str = "__nautilus_vector_cosine_distance";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelationFilterOp {
Some,
None,
Every,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RelationFilter {
pub field: String,
pub parent_table: String,
pub target_table: String,
pub fk_db: String,
pub pk_db: String,
pub filter: Box<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BinaryOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
Like,
ArrayContains,
ArrayContainedBy,
ArrayOverlaps,
In,
NotIn,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Column(String),
Param(Value),
Binary {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
},
Not(Box<Expr>),
FunctionCall {
name: String,
args: Vec<Expr>,
},
Filter {
expr: Box<Expr>,
predicate: Box<Expr>,
},
Exists(Box<Select>),
NotExists(Box<Select>),
Relation {
op: RelationFilterOp,
relation: Box<RelationFilter>,
},
ScalarSubquery(Box<Select>),
IsNull(Box<Expr>),
IsNotNull(Box<Expr>),
Literal(String),
List(Vec<Expr>),
CaseWhen {
condition: Box<Expr>,
then: Box<Expr>,
},
Star,
}
impl Expr {
pub fn column(name: impl Into<String>) -> Self {
Expr::Column(name.into())
}
pub fn param(value: impl Into<Value>) -> Self {
Expr::Param(value.into())
}
#[must_use]
pub fn eq(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Eq,
right: Box::new(other),
}
}
#[must_use]
pub fn ne(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Ne,
right: Box::new(other),
}
}
#[must_use]
pub fn lt(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Lt,
right: Box::new(other),
}
}
#[must_use]
pub fn le(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Le,
right: Box::new(other),
}
}
#[must_use]
pub fn gt(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Gt,
right: Box::new(other),
}
}
#[must_use]
pub fn ge(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Ge,
right: Box::new(other),
}
}
#[must_use]
pub fn and(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::And,
right: Box::new(other),
}
}
#[must_use]
pub fn or(self, other: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Or,
right: Box::new(other),
}
}
#[must_use]
pub fn like(self, pattern: Expr) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::Like,
right: Box::new(pattern),
}
}
#[must_use]
pub fn in_list(self, exprs: Vec<Expr>) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::In,
right: Box::new(Expr::List(exprs)),
}
}
#[must_use]
pub fn not_in_list(self, exprs: Vec<Expr>) -> Self {
Expr::Binary {
left: Box::new(self),
op: BinaryOp::NotIn,
right: Box::new(Expr::List(exprs)),
}
}
pub fn function_call(name: impl Into<String>, args: Vec<Expr>) -> Self {
Expr::FunctionCall {
name: name.into(),
args,
}
}
pub fn vector_distance(metric: crate::args::VectorMetric, left: Expr, right: Expr) -> Self {
let function = match metric {
crate::args::VectorMetric::L2 => VECTOR_L2_DISTANCE_FUNCTION,
crate::args::VectorMetric::InnerProduct => VECTOR_INNER_PRODUCT_FUNCTION,
crate::args::VectorMetric::Cosine => VECTOR_COSINE_DISTANCE_FUNCTION,
};
Expr::function_call(function, vec![left, right])
}
pub fn json_agg(expr: Expr) -> Self {
Expr::FunctionCall {
name: "json_agg".to_string(),
args: vec![expr],
}
}
pub fn json_build_object(pairs: Vec<(String, Expr)>) -> Self {
let args: Vec<Expr> = pairs
.into_iter()
.flat_map(|(key, value)| vec![Expr::Literal(key), value])
.collect();
Expr::FunctionCall {
name: "json_build_object".to_string(),
args,
}
}
pub fn coalesce(exprs: Vec<Expr>) -> Self {
Expr::FunctionCall {
name: "COALESCE".to_string(),
args: exprs,
}
}
#[must_use]
pub fn is_not_null(self) -> Self {
Expr::IsNotNull(Box::new(self))
}
#[must_use]
pub fn is_null(self) -> Self {
Expr::IsNull(Box::new(self))
}
#[must_use]
pub fn filter(self, predicate: Expr) -> Self {
Expr::Filter {
expr: Box::new(self),
predicate: Box::new(predicate),
}
}
pub fn exists(subquery: Select) -> Self {
Expr::Exists(Box::new(subquery))
}
pub fn not_exists(subquery: Select) -> Self {
Expr::NotExists(Box::new(subquery))
}
pub fn relation_some(
field: impl Into<String>,
parent_table: impl Into<String>,
target_table: impl Into<String>,
fk_db: impl Into<String>,
pk_db: impl Into<String>,
filter: Expr,
) -> Self {
Expr::Relation {
op: RelationFilterOp::Some,
relation: Box::new(RelationFilter {
field: field.into(),
parent_table: parent_table.into(),
target_table: target_table.into(),
fk_db: fk_db.into(),
pk_db: pk_db.into(),
filter: Box::new(filter),
}),
}
}
pub fn relation_none(
field: impl Into<String>,
parent_table: impl Into<String>,
target_table: impl Into<String>,
fk_db: impl Into<String>,
pk_db: impl Into<String>,
filter: Expr,
) -> Self {
Expr::Relation {
op: RelationFilterOp::None,
relation: Box::new(RelationFilter {
field: field.into(),
parent_table: parent_table.into(),
target_table: target_table.into(),
fk_db: fk_db.into(),
pk_db: pk_db.into(),
filter: Box::new(filter),
}),
}
}
pub fn relation_every(
field: impl Into<String>,
parent_table: impl Into<String>,
target_table: impl Into<String>,
fk_db: impl Into<String>,
pk_db: impl Into<String>,
filter: Expr,
) -> Self {
Expr::Relation {
op: RelationFilterOp::Every,
relation: Box::new(RelationFilter {
field: field.into(),
parent_table: parent_table.into(),
target_table: target_table.into(),
fk_db: fk_db.into(),
pk_db: pk_db.into(),
filter: Box::new(filter),
}),
}
}
pub fn scalar_subquery(subquery: Select) -> Self {
Expr::ScalarSubquery(Box::new(subquery))
}
pub fn case_when(condition: Expr, then: Expr) -> Self {
Expr::CaseWhen {
condition: Box::new(condition),
then: Box::new(then),
}
}
pub fn star() -> Self {
Expr::Star
}
}
impl std::ops::Not for Expr {
type Output = Self;
fn not(self) -> Self::Output {
Expr::Not(Box::new(self))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_column_expr() {
let expr = Expr::column("email");
match expr {
Expr::Column(name) => assert_eq!(name, "email"),
_ => panic!("Expected Column variant"),
}
}
#[test]
fn test_param_expr() {
let expr = Expr::param(42i64);
match expr {
Expr::Param(Value::I64(42)) => {}
_ => panic!("Expected Param with I64(42)"),
}
}
#[test]
fn test_binary_ops() {
let col = Expr::column("age");
let val = Expr::param(18i64);
let expr = col.ge(val);
match expr {
Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::Ge),
_ => panic!("Expected Binary expression"),
}
}
#[test]
fn test_complex_expr() {
let expr = Expr::column("age")
.ge(Expr::param(18i64))
.and(Expr::column("email").like(Expr::param("%@gmail.com")));
match expr {
Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::And),
_ => panic!("Expected Binary AND expression"),
}
}
#[test]
fn test_not_expr() {
let expr = !Expr::column("active").eq(Expr::param(true));
match expr {
Expr::Not(_) => {}
_ => panic!("Expected Not expression"),
}
}
#[test]
fn test_in_list() {
let expr = Expr::column("status").in_list(vec![
Expr::param(Value::String("active".to_string())),
Expr::param(Value::String("pending".to_string())),
]);
match expr {
Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::In),
_ => panic!("Expected Binary IN expression"),
}
}
#[test]
fn test_not_in_list() {
let expr = Expr::column("role").not_in_list(vec![
Expr::param(Value::String("admin".to_string())),
Expr::param(Value::String("superuser".to_string())),
]);
match expr {
Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::NotIn),
_ => panic!("Expected Binary NOT IN expression"),
}
}
#[test]
fn test_relation_predicate() {
let expr = Expr::relation_some(
"posts",
"users",
"posts",
"author_id",
"id",
Expr::column("posts__published").eq(Expr::param(true)),
);
match expr {
Expr::Relation { op, relation } => {
assert_eq!(op, RelationFilterOp::Some);
assert_eq!(relation.field, "posts");
assert_eq!(relation.parent_table, "users");
assert_eq!(relation.target_table, "posts");
assert_eq!(relation.fk_db, "author_id");
assert_eq!(relation.pk_db, "id");
}
_ => panic!("Expected relation predicate"),
}
}
}