use crate::{db::query::builder::aggregate::AggregateExpr, value::Value};
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(in crate::db) struct FieldId(String);
impl FieldId {
#[must_use]
pub(in crate::db) fn new(field: impl Into<String>) -> Self {
Self(field.into())
}
#[must_use]
pub(in crate::db) 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(in crate::db) struct PathSpec {
root: FieldId,
path: Vec<String>,
}
impl PathSpec {
#[must_use]
pub(in crate::db) fn new(root: impl Into<FieldId>, segments: Vec<String>) -> Self {
debug_assert!(
!segments.is_empty(),
"field paths must contain at least one nested segment"
);
Self {
root: root.into(),
path: segments,
}
}
#[must_use]
pub(in crate::db) const fn root(&self) -> &FieldId {
&self.root
}
#[must_use]
pub(in crate::db) const fn segments(&self) -> &[String] {
self.path.as_slice()
}
#[must_use]
pub(in crate::db) const fn is_scalar_leaf(&self) -> bool {
!self.path.is_empty()
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(in crate::db) struct FieldPath {
path: PathSpec,
}
impl FieldPath {
#[must_use]
pub(in crate::db) fn new(root: impl Into<FieldId>, segments: Vec<String>) -> Self {
Self {
path: PathSpec::new(root, segments),
}
}
#[must_use]
pub(in crate::db) const fn path_spec(&self) -> &PathSpec {
&self.path
}
#[must_use]
pub(in crate::db) const fn root(&self) -> &FieldId {
self.path.root()
}
#[must_use]
pub(in crate::db) const fn segments(&self) -> &[String] {
self.path.segments()
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(in crate::db) struct Alias(String);
impl Alias {
#[must_use]
pub(in crate::db) fn new(name: impl Into<String>) -> Self {
Self(name.into())
}
#[must_use]
pub(in crate::db) 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)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum UnaryOp {
Not,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum BinaryOp {
Or,
And,
Eq,
Ne,
Lt,
Lte,
Gt,
Gte,
Add,
Sub,
Mul,
Div,
}
impl BinaryOp {
#[must_use]
pub(in crate::db) const fn canonical_label(self) -> &'static str {
match self {
Self::Or => "or",
Self::And => "and",
Self::Eq => "eq",
Self::Ne => "ne",
Self::Lt => "lt",
Self::Lte => "lte",
Self::Gt => "gt",
Self::Gte => "gte",
Self::Add => "add",
Self::Sub => "sub",
Self::Mul => "mul",
Self::Div => "div",
}
}
#[must_use]
pub(in crate::db) const fn is_numeric_arithmetic(self) -> bool {
matches!(self, Self::Add | Self::Sub | Self::Mul | Self::Div)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[remain::sorted]
pub(in crate::db) enum Function {
Abs,
Cbrt,
Ceiling,
Coalesce,
CollectionContains,
Contains,
EndsWith,
Exp,
Floor,
IsEmpty,
IsMissing,
IsNotEmpty,
IsNotNull,
IsNull,
Left,
Length,
Ln,
Log,
Log2,
Log10,
Lower,
Ltrim,
Mod,
NullIf,
OctetLength,
Position,
Power,
Replace,
Right,
Round,
Rtrim,
Sign,
Sqrt,
StartsWith,
Substring,
Trim,
Trunc,
Upper,
}
impl Function {
#[must_use]
pub(in crate::db) const fn canonical_label(self) -> &'static str {
match self {
Self::Abs => "ABS",
Self::Cbrt => "CBRT",
Self::Ceiling => "CEILING",
Self::Coalesce => "COALESCE",
Self::CollectionContains => "COLLECTION_CONTAINS",
Self::Contains => "CONTAINS",
Self::EndsWith => "ENDS_WITH",
Self::Exp => "EXP",
Self::Floor => "FLOOR",
Self::IsEmpty => "IS_EMPTY",
Self::IsMissing => "IS_MISSING",
Self::IsNotEmpty => "IS_NOT_EMPTY",
Self::IsNotNull => "IS_NOT_NULL",
Self::IsNull => "IS_NULL",
Self::Left => "LEFT",
Self::Length => "LENGTH",
Self::Ln => "LN",
Self::Log => "LOG",
Self::Log10 => "LOG10",
Self::Log2 => "LOG2",
Self::Lower => "LOWER",
Self::Ltrim => "LTRIM",
Self::Mod => "MOD",
Self::NullIf => "NULLIF",
Self::OctetLength => "OCTET_LENGTH",
Self::Position => "POSITION",
Self::Power => "POWER",
Self::Replace => "REPLACE",
Self::Round => "ROUND",
Self::Right => "RIGHT",
Self::Rtrim => "RTRIM",
Self::Sign => "SIGN",
Self::StartsWith => "STARTS_WITH",
Self::Substring => "SUBSTRING",
Self::Sqrt => "SQRT",
Self::Trim => "TRIM",
Self::Trunc => "TRUNC",
Self::Upper => "UPPER",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) struct CaseWhenArm {
condition: Expr,
result: Expr,
}
impl CaseWhenArm {
#[must_use]
pub(in crate::db) const fn new(condition: Expr, result: Expr) -> Self {
Self { condition, result }
}
#[must_use]
pub(in crate::db) const fn condition(&self) -> &Expr {
&self.condition
}
#[must_use]
pub(in crate::db) const fn result(&self) -> &Expr {
&self.result
}
}
#[must_use]
#[cfg(test)]
pub(in crate::db) const fn supported_order_expr_is_plain_field(expr: &Expr) -> bool {
matches!(expr, Expr::Field(_))
}
#[must_use]
#[cfg(test)]
pub(in crate::db) fn supported_order_expr_field(expr: &Expr) -> Option<&FieldId> {
match expr {
Expr::FunctionCall {
function:
Function::Trim
| Function::Ltrim
| Function::Rtrim
| Function::Abs
| Function::Ceiling
| Function::Floor
| Function::Sign
| Function::Sqrt
| Function::Lower
| Function::Upper
| Function::Length
| Function::OctetLength,
args,
} => match args.as_slice() {
[Expr::Field(field)] => Some(field),
_ => None,
},
_ => None,
}
}
#[must_use]
#[cfg(test)]
pub(in crate::db) fn render_supported_order_expr(expr: &Expr) -> Option<String> {
render_supported_order_expr_with_parent(expr, None)
}
#[must_use]
pub(in crate::db) const fn supported_order_expr_requires_index_satisfied_access(
expr: &Expr,
) -> bool {
matches!(
expr,
Expr::FunctionCall {
function,
args,
} if function.is_casefold_transform() && matches!(args.as_slice(), [Expr::Field(_)])
)
}
#[cfg(test)]
fn render_supported_order_function(function: Function, args: &[Expr]) -> Option<String> {
match supported_order_function_shape(function)? {
SupportedOrderFunctionShape::UnaryExpr => match args {
[arg] => Some(format!(
"{}({})",
function.canonical_label(),
render_supported_order_expr_with_parent(arg, None)?
)),
_ => None,
},
SupportedOrderFunctionShape::VariadicExprMin2 => {
if args.len() < 2 {
return None;
}
let rendered = args
.iter()
.map(|arg| render_supported_order_expr_with_parent(arg, None))
.collect::<Option<Vec<_>>>()?;
Some(format!(
"{}({})",
function.canonical_label(),
rendered.join(", ")
))
}
SupportedOrderFunctionShape::BinaryExpr => match args {
[left, right] => Some(format!(
"{}({}, {})",
function.canonical_label(),
render_supported_order_expr_with_parent(left, None)?,
render_supported_order_expr_with_parent(right, None)?
)),
_ => None,
},
SupportedOrderFunctionShape::FieldLiteral => match args {
[Expr::Field(field), Expr::Literal(literal)] => Some(format!(
"{}({}, {})",
function.canonical_label(),
field.as_str(),
render_supported_order_literal(literal)?
)),
_ => None,
},
SupportedOrderFunctionShape::LiteralField => match args {
[Expr::Literal(literal), Expr::Field(field)] => Some(format!(
"{}({}, {})",
function.canonical_label(),
render_supported_order_literal(literal)?,
field.as_str(),
)),
_ => None,
},
SupportedOrderFunctionShape::FieldTwoLiterals => match args {
[Expr::Field(field), Expr::Literal(from), Expr::Literal(to)] => Some(format!(
"{}({}, {}, {})",
function.canonical_label(),
field.as_str(),
render_supported_order_literal(from)?,
render_supported_order_literal(to)?,
)),
_ => None,
},
SupportedOrderFunctionShape::FieldOneOrTwoLiterals => match args {
[Expr::Field(field), Expr::Literal(start)] => Some(format!(
"{}({}, {})",
function.canonical_label(),
field.as_str(),
render_supported_order_literal(start)?,
)),
[
Expr::Field(field),
Expr::Literal(start),
Expr::Literal(length),
] => Some(format!(
"{}({}, {}, {})",
function.canonical_label(),
field.as_str(),
render_supported_order_literal(start)?,
render_supported_order_literal(length)?,
)),
_ => None,
},
SupportedOrderFunctionShape::Round => None,
}
}
#[cfg(test)]
fn render_supported_order_expr_with_parent(
expr: &Expr,
parent_op: Option<BinaryOp>,
) -> Option<String> {
match expr {
Expr::Binary { op, left, right }
if matches!(
op,
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div
) =>
{
let left = render_supported_order_expr_with_parent(left.as_ref(), Some(*op))?;
let right = render_supported_order_expr_with_parent(right.as_ref(), Some(*op))?;
let rendered = format!("{left} {} {right}", binary_op_symbol(*op));
if binary_expr_requires_parentheses(*op, parent_op) {
Some(format!("({rendered})"))
} else {
Some(rendered)
}
}
Expr::FunctionCall {
function: Function::Round,
args,
} => match args.as_slice() {
[base, Expr::Literal(scale)] => Some(format!(
"ROUND({}, {})",
render_supported_order_expr_with_parent(base, None)?,
render_supported_order_literal(scale)?
)),
_ => None,
},
Expr::FunctionCall { function, args } => render_supported_order_function(*function, args),
Expr::Field(field) => Some(field.as_str().to_string()),
Expr::Literal(value) => render_supported_order_literal(value),
Expr::FieldPath(_) | Expr::Binary { .. } | Expr::Aggregate(_) | Expr::Case { .. } => None,
Expr::Unary { .. } => None,
#[cfg(test)]
Expr::Alias { .. } => None,
}
}
#[cfg(test)]
const fn binary_expr_requires_parentheses(op: BinaryOp, parent_op: Option<BinaryOp>) -> bool {
let Some(parent_op) = parent_op else {
return false;
};
binary_op_precedence(op) < binary_op_precedence(parent_op)
}
#[cfg(test)]
const fn binary_op_precedence(op: BinaryOp) -> u8 {
match op {
BinaryOp::Or => 0,
BinaryOp::And => 1,
BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Lte
| BinaryOp::Gt
| BinaryOp::Gte => 2,
BinaryOp::Add | BinaryOp::Sub => 3,
BinaryOp::Mul | BinaryOp::Div => 4,
}
}
#[cfg(test)]
const fn binary_op_symbol(op: BinaryOp) -> &'static str {
match op {
BinaryOp::Or => "OR",
BinaryOp::And => "AND",
BinaryOp::Eq => "=",
BinaryOp::Ne => "!=",
BinaryOp::Lt => "<",
BinaryOp::Lte => "<=",
BinaryOp::Gt => ">",
BinaryOp::Gte => ">=",
BinaryOp::Add => "+",
BinaryOp::Sub => "-",
BinaryOp::Mul => "*",
BinaryOp::Div => "/",
}
}
#[cfg(test)]
fn render_supported_order_literal(value: &Value) -> Option<String> {
Some(match value {
Value::Null => "NULL".to_string(),
Value::Text(text) => format!("'{}'", text.replace('\'', "''")),
Value::Int(value) => value.to_string(),
Value::Int128(value) => value.to_string(),
Value::IntBig(value) => value.to_string(),
Value::Uint(value) => value.to_string(),
Value::Uint128(value) => value.to_string(),
Value::UintBig(value) => value.to_string(),
Value::Decimal(value) => value.to_string(),
Value::Float32(value) => value.to_string(),
Value::Float64(value) => value.to_string(),
Value::Bool(value) => value.to_string().to_uppercase(),
_ => return None,
})
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg(test)]
enum SupportedOrderFunctionShape {
UnaryExpr,
VariadicExprMin2,
BinaryExpr,
FieldLiteral,
LiteralField,
FieldTwoLiterals,
FieldOneOrTwoLiterals,
Round,
}
#[cfg(test)]
const fn supported_order_function_shape(function: Function) -> Option<SupportedOrderFunctionShape> {
match function {
Function::IsNull
| Function::IsNotNull
| Function::IsMissing
| Function::IsEmpty
| Function::IsNotEmpty
| Function::Trim
| Function::Ltrim
| Function::Rtrim
| Function::Abs
| Function::Cbrt
| Function::Ceiling
| Function::Exp
| Function::Floor
| Function::Ln
| Function::Log2
| Function::Log10
| Function::Sign
| Function::Sqrt
| Function::Lower
| Function::Upper
| Function::Length
| Function::OctetLength => Some(SupportedOrderFunctionShape::UnaryExpr),
Function::Coalesce => Some(SupportedOrderFunctionShape::VariadicExprMin2),
Function::NullIf | Function::Log | Function::Mod | Function::Power => {
Some(SupportedOrderFunctionShape::BinaryExpr)
}
Function::Left
| Function::Right
| Function::StartsWith
| Function::EndsWith
| Function::Contains => Some(SupportedOrderFunctionShape::FieldLiteral),
Function::Position => Some(SupportedOrderFunctionShape::LiteralField),
Function::Replace => Some(SupportedOrderFunctionShape::FieldTwoLiterals),
Function::Substring => Some(SupportedOrderFunctionShape::FieldOneOrTwoLiterals),
Function::Round | Function::Trunc => Some(SupportedOrderFunctionShape::Round),
Function::CollectionContains => None,
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db) enum Expr {
Field(FieldId),
FieldPath(FieldPath),
Literal(Value),
FunctionCall {
function: Function,
args: Vec<Self>,
},
Unary {
op: UnaryOp,
expr: Box<Self>,
},
Binary {
op: BinaryOp,
left: Box<Self>,
right: Box<Self>,
},
Case {
when_then_arms: Vec<CaseWhenArm>,
else_expr: Box<Self>,
},
Aggregate(AggregateExpr),
#[cfg(test)]
Alias {
expr: Box<Self>,
name: Alias,
},
}
impl Expr {
#[must_use]
pub(in crate::db) fn contains_aggregate(&self) -> bool {
self.any_tree_expr(&mut |expr| matches!(expr, Self::Aggregate(_)))
}
#[must_use]
pub(in crate::db) fn contains_case(&self) -> bool {
self.any_tree_expr(&mut |expr| matches!(expr, Self::Case { .. }))
}
#[must_use]
pub(in crate::db) fn any_tree_expr(&self, predicate: &mut impl FnMut(&Self) -> bool) -> bool {
if predicate(self) {
return true;
}
match self {
Self::Field(_) | Self::FieldPath(_) | Self::Literal(_) | Self::Aggregate(_) => false,
Self::FunctionCall { args, .. } => args.iter().any(|arg| arg.any_tree_expr(predicate)),
Self::Unary { expr, .. } => expr.any_tree_expr(predicate),
Self::Binary { left, right, .. } => {
left.any_tree_expr(predicate) || right.any_tree_expr(predicate)
}
Self::Case {
when_then_arms,
else_expr,
} => {
when_then_arms.iter().any(|arm| {
arm.condition().any_tree_expr(predicate)
|| arm.result().any_tree_expr(predicate)
}) || else_expr.any_tree_expr(predicate)
}
#[cfg(test)]
Self::Alias { expr, .. } => expr.any_tree_expr(predicate),
}
}
#[must_use]
pub(in crate::db) fn all_tree_expr(&self, predicate: &mut impl FnMut(&Self) -> bool) -> bool {
if !predicate(self) {
return false;
}
match self {
Self::Field(_) | Self::FieldPath(_) | Self::Literal(_) | Self::Aggregate(_) => true,
Self::FunctionCall { args, .. } => args.iter().all(|arg| arg.all_tree_expr(predicate)),
Self::Unary { expr, .. } => expr.all_tree_expr(predicate),
Self::Binary { left, right, .. } => {
left.all_tree_expr(predicate) && right.all_tree_expr(predicate)
}
Self::Case {
when_then_arms,
else_expr,
} => {
when_then_arms.iter().all(|arm| {
arm.condition().all_tree_expr(predicate)
&& arm.result().all_tree_expr(predicate)
}) && else_expr.all_tree_expr(predicate)
}
#[cfg(test)]
Self::Alias { expr, .. } => expr.all_tree_expr(predicate),
}
}
pub(in crate::db) fn try_for_each_tree_expr<E>(
&self,
visit: &mut impl FnMut(&Self) -> Result<(), E>,
) -> Result<(), E> {
visit(self)?;
match self {
Self::Field(_) | Self::FieldPath(_) | Self::Literal(_) | Self::Aggregate(_) => Ok(()),
Self::FunctionCall { args, .. } => {
for arg in args {
arg.try_for_each_tree_expr(visit)?;
}
Ok(())
}
Self::Unary { expr, .. } => expr.try_for_each_tree_expr(visit),
Self::Binary { left, right, .. } => {
left.try_for_each_tree_expr(visit)?;
right.try_for_each_tree_expr(visit)
}
Self::Case {
when_then_arms,
else_expr,
} => {
for arm in when_then_arms {
arm.condition().try_for_each_tree_expr(visit)?;
arm.result().try_for_each_tree_expr(visit)?;
}
else_expr.try_for_each_tree_expr(visit)
}
#[cfg(test)]
Self::Alias { expr, .. } => expr.try_for_each_tree_expr(visit),
}
}
pub(in crate::db) fn try_for_each_tree_aggregate<E>(
&self,
visit: &mut impl FnMut(&AggregateExpr) -> Result<(), E>,
) -> Result<(), E> {
self.try_for_each_tree_expr(&mut |expr| match expr {
Self::Aggregate(aggregate) => visit(aggregate),
_ => Ok(()),
})
}
pub(in crate::db) fn try_for_each_tree_expr_with_compare_index<E>(
&self,
next_compare_index: &mut usize,
visit: &mut impl FnMut(usize, &Self) -> Result<(), E>,
) -> Result<(), E> {
match self {
Self::Field(_) | Self::FieldPath(_) | Self::Literal(_) | Self::Aggregate(_) => {}
Self::FunctionCall { args, .. } => {
for arg in args {
arg.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
}
Self::Unary { expr, .. } => {
expr.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
Self::Binary { left, right, .. } => {
left.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
right.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
Self::Case {
when_then_arms,
else_expr,
} => {
for arm in when_then_arms {
arm.condition()
.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
arm.result()
.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
else_expr.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
#[cfg(test)]
Self::Alias { expr, .. } => {
expr.try_for_each_tree_expr_with_compare_index(next_compare_index, visit)?;
}
}
let current_index = *next_compare_index;
visit(current_index, self)?;
if matches!(
self,
Self::Binary {
op: BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::Lt
| BinaryOp::Lte
| BinaryOp::Gt
| BinaryOp::Gte,
..
}
) {
*next_compare_index = next_compare_index.saturating_add(1);
}
Ok(())
}
#[must_use]
pub(in crate::db) fn references_only_fields(&self, allowed: &[&str]) -> bool {
self.all_tree_expr(&mut |expr| match expr {
Self::Field(field) => allowed.iter().any(|allowed| *allowed == field.as_str()),
Self::FieldPath(_) => false,
Self::Aggregate(_) | Self::Literal(_) => true,
Self::FunctionCall { .. }
| Self::Unary { .. }
| Self::Binary { .. }
| Self::Case { .. } => true,
#[cfg(test)]
Self::Alias { .. } => true,
})
}
}