use std::fmt;
use std::ops::Not;
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Null,
Boolean(bool),
Integer(i64),
Float(f64),
String(String),
Vector(Vec<f32>),
MultiVector(Vec<Vec<f32>>),
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => write!(f, "NULL"),
Self::Boolean(b) => write!(f, "{b}"),
Self::Integer(i) => write!(f, "{i}"),
Self::Float(fl) => write!(f, "{fl}"),
Self::String(s) => write!(f, "'{s}'"),
Self::Vector(v) => {
write!(f, "[")?;
for (i, val) in v.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{val}")?;
}
write!(f, "]")
}
Self::MultiVector(vecs) => {
write!(f, "[")?;
for (i, vec) in vecs.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "[")?;
for (j, val) in vec.iter().enumerate() {
if j > 0 {
write!(f, ", ")?;
}
write!(f, "{val}")?;
}
write!(f, "]")?;
}
write!(f, "]")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Identifier {
pub name: String,
pub quote_style: Option<char>,
}
impl Identifier {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into(), quote_style: None }
}
#[must_use]
pub fn quoted(name: impl Into<String>, quote: char) -> Self {
Self { name: name.into(), quote_style: Some(quote) }
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.quote_style {
Some(q) => write!(f, "{q}{}{q}", self.name),
None => write!(f, "{}", self.name),
}
}
}
impl From<&str> for Identifier {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for Identifier {
fn from(s: String) -> Self {
Self::new(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QualifiedName {
pub parts: Vec<Identifier>,
}
impl QualifiedName {
#[must_use]
pub const fn new(parts: Vec<Identifier>) -> Self {
Self { parts }
}
#[must_use]
pub fn simple(name: impl Into<Identifier>) -> Self {
Self { parts: vec![name.into()] }
}
#[must_use]
pub fn qualified(qualifier: impl Into<Identifier>, name: impl Into<Identifier>) -> Self {
Self { parts: vec![qualifier.into(), name.into()] }
}
#[must_use]
pub fn name(&self) -> Option<&Identifier> {
self.parts.last()
}
#[must_use]
pub fn qualifiers(&self) -> &[Identifier] {
if self.parts.is_empty() {
&[]
} else {
&self.parts[..self.parts.len() - 1]
}
}
}
impl fmt::Display for QualifiedName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, part) in self.parts.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{part}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Mod,
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
And,
Or,
Like,
NotLike,
ILike,
NotILike,
EuclideanDistance,
CosineDistance,
InnerProduct,
MaxSim,
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op = match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Mod => "%",
Self::Eq => "=",
Self::NotEq => "<>",
Self::Lt => "<",
Self::LtEq => "<=",
Self::Gt => ">",
Self::GtEq => ">=",
Self::And => "AND",
Self::Or => "OR",
Self::Like => "LIKE",
Self::NotLike => "NOT LIKE",
Self::ILike => "ILIKE",
Self::NotILike => "NOT ILIKE",
Self::EuclideanDistance => "<->",
Self::CosineDistance => "<=>",
Self::InnerProduct => "<#>",
Self::MaxSim => "<##>",
};
write!(f, "{op}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
Not,
Neg,
IsNull,
IsNotNull,
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op = match self {
Self::Not => "NOT",
Self::Neg => "-",
Self::IsNull => "IS NULL",
Self::IsNotNull => "IS NOT NULL",
};
write!(f, "{op}")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionCall {
pub name: QualifiedName,
pub args: Vec<Expr>,
pub distinct: bool,
pub filter: Option<Box<Expr>>,
pub over: Option<WindowSpec>,
}
impl FunctionCall {
#[must_use]
pub fn new(name: impl Into<QualifiedName>, args: Vec<Expr>) -> Self {
Self { name: name.into(), args, distinct: false, filter: None, over: None }
}
}
impl From<QualifiedName> for FunctionCall {
fn from(name: QualifiedName) -> Self {
Self::new(name, vec![])
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowSpec {
pub partition_by: Vec<Expr>,
pub order_by: Vec<OrderByExpr>,
pub frame: Option<WindowFrame>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WindowFrame {
pub units: WindowFrameUnits,
pub start: WindowFrameBound,
pub end: Option<WindowFrameBound>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowFrameUnits {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WindowFrameBound {
CurrentRow,
UnboundedPreceding,
UnboundedFollowing,
Preceding(Box<Expr>),
Following(Box<Expr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderByExpr {
pub expr: Box<Expr>,
pub asc: bool,
pub nulls_first: Option<bool>,
}
impl OrderByExpr {
#[must_use]
pub fn asc(expr: Expr) -> Self {
Self { expr: Box::new(expr), asc: true, nulls_first: None }
}
#[must_use]
pub fn desc(expr: Expr) -> Self {
Self { expr: Box::new(expr), asc: false, nulls_first: None }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CaseExpr {
pub operand: Option<Box<Expr>>,
pub when_clauses: Vec<(Expr, Expr)>,
pub else_result: Option<Box<Expr>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Subquery {
pub query: Box<super::statement::SelectStatement>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal),
Column(QualifiedName),
Parameter(ParameterRef),
BinaryOp {
left: Box<Expr>,
op: BinaryOp,
right: Box<Expr>,
},
UnaryOp {
op: UnaryOp,
operand: Box<Expr>,
},
Function(FunctionCall),
Cast {
expr: Box<Expr>,
data_type: String,
},
Case(CaseExpr),
Subquery(Subquery),
Exists(Subquery),
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
subquery: Subquery,
negated: bool,
},
Between {
expr: Box<Expr>,
low: Box<Expr>,
high: Box<Expr>,
negated: bool,
},
ArrayIndex {
array: Box<Expr>,
index: Box<Expr>,
},
Tuple(Vec<Expr>),
Wildcard,
QualifiedWildcard(QualifiedName),
HybridSearch {
components: Vec<HybridSearchComponent>,
method: HybridCombinationMethod,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct HybridSearchComponent {
pub distance_expr: Box<Expr>,
pub weight: f64,
}
impl HybridSearchComponent {
#[must_use]
pub fn new(distance_expr: Expr, weight: f64) -> Self {
Self { distance_expr: Box::new(distance_expr), weight }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HybridCombinationMethod {
WeightedSum,
RRF {
k: u32,
},
}
impl Default for HybridCombinationMethod {
fn default() -> Self {
Self::WeightedSum
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ParameterRef {
Positional(u32),
Named(String),
Anonymous,
}
impl fmt::Display for ParameterRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Positional(n) => write!(f, "${n}"),
Self::Named(name) => write!(f, "${name}"),
Self::Anonymous => write!(f, "?"),
}
}
}
impl Expr {
#[must_use]
pub const fn null() -> Self {
Self::Literal(Literal::Null)
}
#[must_use]
pub const fn boolean(value: bool) -> Self {
Self::Literal(Literal::Boolean(value))
}
#[must_use]
pub const fn integer(value: i64) -> Self {
Self::Literal(Literal::Integer(value))
}
#[must_use]
pub const fn float(value: f64) -> Self {
Self::Literal(Literal::Float(value))
}
#[must_use]
pub fn string(value: impl Into<String>) -> Self {
Self::Literal(Literal::String(value.into()))
}
#[must_use]
pub fn column(name: impl Into<QualifiedName>) -> Self {
Self::Column(name.into())
}
#[must_use]
pub fn binary(left: Self, op: BinaryOp, right: Self) -> Self {
Self::BinaryOp { left: Box::new(left), op, right: Box::new(right) }
}
#[must_use]
pub fn unary(op: UnaryOp, operand: Self) -> Self {
Self::UnaryOp { op, operand: Box::new(operand) }
}
#[must_use]
pub fn function(name: impl Into<QualifiedName>, args: Vec<Self>) -> Self {
Self::Function(FunctionCall::new(name, args))
}
#[must_use]
pub fn and(self, other: Self) -> Self {
Self::binary(self, BinaryOp::And, other)
}
#[must_use]
pub fn or(self, other: Self) -> Self {
Self::binary(self, BinaryOp::Or, other)
}
#[must_use]
pub fn negate(self) -> Self {
Self::unary(UnaryOp::Not, self)
}
#[must_use]
pub fn eq(self, other: Self) -> Self {
Self::binary(self, BinaryOp::Eq, other)
}
#[must_use]
pub fn not_eq(self, other: Self) -> Self {
Self::binary(self, BinaryOp::NotEq, other)
}
#[must_use]
pub fn lt(self, other: Self) -> Self {
Self::binary(self, BinaryOp::Lt, other)
}
#[must_use]
pub fn gt(self, other: Self) -> Self {
Self::binary(self, BinaryOp::Gt, other)
}
}
impl From<i64> for Expr {
fn from(value: i64) -> Self {
Self::integer(value)
}
}
impl From<f64> for Expr {
fn from(value: f64) -> Self {
Self::float(value)
}
}
impl From<bool> for Expr {
fn from(value: bool) -> Self {
Self::boolean(value)
}
}
impl From<&str> for Expr {
fn from(value: &str) -> Self {
Self::string(value)
}
}
impl From<String> for Expr {
fn from(value: String) -> Self {
Self::string(value)
}
}
impl From<QualifiedName> for Expr {
fn from(name: QualifiedName) -> Self {
Self::Column(name)
}
}
impl Not for Expr {
type Output = Self;
fn not(self) -> Self::Output {
self.negate()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn literal_display() {
assert_eq!(Literal::Null.to_string(), "NULL");
assert_eq!(Literal::Boolean(true).to_string(), "true");
assert_eq!(Literal::Integer(42).to_string(), "42");
assert_eq!(Literal::Float(1.5).to_string(), "1.5");
assert_eq!(Literal::String("hello".into()).to_string(), "'hello'");
assert_eq!(Literal::Vector(vec![1.0, 2.0, 3.0]).to_string(), "[1, 2, 3]");
assert_eq!(
Literal::MultiVector(vec![vec![0.1, 0.2], vec![0.3, 0.4]]).to_string(),
"[[0.1, 0.2], [0.3, 0.4]]"
);
}
#[test]
fn identifier_display() {
assert_eq!(Identifier::new("foo").to_string(), "foo");
assert_eq!(Identifier::quoted("Foo", '"').to_string(), "\"Foo\"");
}
#[test]
fn qualified_name() {
let simple = QualifiedName::simple("column");
assert_eq!(simple.to_string(), "column");
let qualified = QualifiedName::qualified("table", "column");
assert_eq!(qualified.to_string(), "table.column");
assert_eq!(qualified.name().map(|i| i.name.as_str()), Some("column"));
assert_eq!(qualified.qualifiers().len(), 1);
}
#[test]
fn expr_builders() {
let expr = Expr::column(QualifiedName::simple("id"))
.eq(Expr::integer(42))
.and(Expr::column(QualifiedName::simple("active")).eq(Expr::boolean(true)));
match expr {
Expr::BinaryOp { op: BinaryOp::And, .. } => (),
_ => panic!("expected AND expression"),
}
}
#[test]
fn binary_op_display() {
assert_eq!(BinaryOp::EuclideanDistance.to_string(), "<->");
assert_eq!(BinaryOp::CosineDistance.to_string(), "<=>");
assert_eq!(BinaryOp::InnerProduct.to_string(), "<#>");
assert_eq!(BinaryOp::MaxSim.to_string(), "<##>");
}
#[test]
fn parameter_display() {
assert_eq!(ParameterRef::Positional(1).to_string(), "$1");
assert_eq!(ParameterRef::Named("query".into()).to_string(), "$query");
assert_eq!(ParameterRef::Anonymous.to_string(), "?");
}
}