use crate::db::query::builder::aggregate::AggregateExpr;
use crate::value::Value;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct FieldId(String);
impl FieldId {
#[must_use]
pub(crate) fn new(field: impl Into<String>) -> Self {
Self(field.into())
}
#[must_use]
pub(crate) const fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for FieldId {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for FieldId {
fn from(value: String) -> Self {
Self::new(value)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct Alias(String);
impl Alias {
#[must_use]
pub(crate) fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
#[must_use]
pub(crate) const fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for Alias {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for Alias {
fn from(value: String) -> Self {
Self::new(value)
}
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum UnaryOp {
Not,
}
#[cfg(test)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BinaryOp {
Add,
Mul,
And,
Eq,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum Function {
Trim,
Ltrim,
Rtrim,
Lower,
Upper,
Length,
Left,
Right,
StartsWith,
EndsWith,
Contains,
Position,
Replace,
Substring,
}
impl Function {
#[must_use]
pub(crate) const fn sql_label(self) -> &'static str {
match self {
Self::Trim => "TRIM",
Self::Ltrim => "LTRIM",
Self::Rtrim => "RTRIM",
Self::Lower => "LOWER",
Self::Upper => "UPPER",
Self::Length => "LENGTH",
Self::Left => "LEFT",
Self::Right => "RIGHT",
Self::StartsWith => "STARTS_WITH",
Self::EndsWith => "ENDS_WITH",
Self::Contains => "CONTAINS",
Self::Position => "POSITION",
Self::Replace => "REPLACE",
Self::Substring => "SUBSTRING",
}
}
}
#[must_use]
pub(in crate::db) fn parse_supported_order_expr(term: &str) -> Option<Expr> {
let open_index = term.find('(')?;
if !term.ends_with(')') {
return None;
}
let function = match &term[..open_index] {
name if name.eq_ignore_ascii_case("LOWER") => Function::Lower,
name if name.eq_ignore_ascii_case("UPPER") => Function::Upper,
_ => return None,
};
let field = &term[open_index.saturating_add(1)..term.len().saturating_sub(1)];
Some(Expr::FunctionCall {
function,
args: vec![Expr::Field(FieldId::new(field))],
})
}
#[must_use]
pub(in crate::db) fn supported_order_expr_field(expr: &Expr) -> Option<&FieldId> {
match expr {
Expr::FunctionCall {
function: Function::Lower | Function::Upper,
args,
} => match args.as_slice() {
[Expr::Field(field)] => Some(field),
_ => None,
},
_ => None,
}
}
#[must_use]
pub(in crate::db) fn rewrite_supported_order_expr_field(
expr: &Expr,
field: impl Into<String>,
) -> Option<Expr> {
let function = match expr {
Expr::FunctionCall {
function: function @ (Function::Lower | Function::Upper),
args,
} if matches!(args.as_slice(), [Expr::Field(_)]) => *function,
_ => return None,
};
Some(Expr::FunctionCall {
function,
args: vec![Expr::Field(FieldId::new(field))],
})
}
#[must_use]
pub(in crate::db) fn render_supported_order_expr(expr: &Expr) -> Option<String> {
let function = match expr {
Expr::FunctionCall {
function: function @ (Function::Lower | Function::Upper),
args,
} if matches!(args.as_slice(), [Expr::Field(_)]) => *function,
_ => return None,
};
let field = supported_order_expr_field(expr)?;
Some(format!("{}({})", function.sql_label(), field.as_str()))
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Expr {
Field(FieldId),
Literal(Value),
FunctionCall {
function: Function,
args: Vec<Self>,
},
#[cfg(test)]
Unary {
op: UnaryOp,
expr: Box<Self>,
},
#[cfg(test)]
Binary {
op: BinaryOp,
left: Box<Self>,
right: Box<Self>,
},
Aggregate(AggregateExpr),
#[cfg(test)]
Alias {
expr: Box<Self>,
name: Alias,
},
}