use crate::provider::DbValue;
use super::compile::collect_bool_expr_values;
#[derive(Debug, Clone)]
pub struct FilterCondition {
column: String,
operator: String,
param_count: usize,
pub(crate) values: Vec<DbValue>,
}
impl FilterCondition {
pub fn new(column: impl Into<String>, operator: impl Into<String>, param_count: usize) -> Self {
Self {
column: column.into(),
operator: operator.into(),
param_count,
values: Vec::new(),
}
}
pub fn with_values(
column: impl Into<String>,
operator: impl Into<String>,
values: Vec<DbValue>,
) -> Self {
let count = values.len();
Self {
column: column.into(),
operator: operator.into(),
param_count: count,
values,
}
}
pub fn values(&self) -> &[DbValue] {
&self.values
}
pub fn to_sql(&self, placeholders: &[String]) -> String {
match self.operator.as_str() {
"IS NULL" => format!("{} IS NULL", self.column),
"IS NOT NULL" => format!("{} IS NOT NULL", self.column),
"IN" => format!("{} IN ({})", self.column, placeholders.join(", ")),
"BETWEEN" if placeholders.len() >= 2 => format!(
"{} BETWEEN {} AND {}",
self.column, placeholders[0], placeholders[1]
),
op if self.param_count == 0 => format!("{} {}", self.column, op),
op => format!("{} {} {}", self.column, op, placeholders[0]),
}
}
pub fn param_count(&self) -> usize {
self.param_count
}
pub fn column(&self) -> &str {
&self.column
}
pub fn operator(&self) -> &str {
&self.operator
}
}
#[derive(Debug, Clone)]
pub struct RawSql(pub(crate) String);
#[derive(Debug, Clone)]
pub enum BoolExpr {
Filter(FilterCondition),
Raw(RawSql),
And(Box<BoolExpr>, Box<BoolExpr>),
Or(Box<BoolExpr>, Box<BoolExpr>),
Not(Box<BoolExpr>),
Exists(Box<SubquerySpec>),
NotExists(Box<SubquerySpec>),
InSubquery(Box<InSubquerySpec>),
NotInSubquery(Box<InSubquerySpec>),
}
#[derive(Debug, Clone)]
pub struct SubquerySpec {
pub navigation_field: String,
pub related_type_name: String,
pub predicate: Option<Box<BoolExpr>>,
pub outer_table: String,
pub related_table: String,
pub fk_column: String,
pub outer_pk_column: String,
}
impl SubquerySpec {
pub fn new(navigation_field: impl Into<String>, related_type_name: impl Into<String>) -> Self {
Self {
navigation_field: navigation_field.into(),
related_type_name: related_type_name.into(),
predicate: None,
outer_table: String::new(),
related_table: String::new(),
fk_column: String::new(),
outer_pk_column: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct InSubquerySpec {
pub outer_column: String,
pub source_table: String,
pub projection_column: String,
pub predicate: Option<Box<BoolExpr>>,
}
impl InSubquerySpec {
pub fn new(
outer_column: impl Into<String>,
source_table: impl Into<String>,
projection_column: impl Into<String>,
) -> Self {
Self {
outer_column: outer_column.into(),
source_table: source_table.into(),
projection_column: projection_column.into(),
predicate: None,
}
}
}
impl BoolExpr {
pub fn filter(
column: impl Into<String>,
operator: impl Into<String>,
param_count: usize,
) -> Self {
BoolExpr::Filter(FilterCondition::new(column, operator, param_count))
}
pub(crate) fn raw(sql: impl Into<String>) -> Self {
BoolExpr::Raw(RawSql(sql.into()))
}
pub fn and(self, other: BoolExpr) -> Self {
BoolExpr::And(Box::new(self), Box::new(other))
}
pub fn or(self, other: BoolExpr) -> Self {
BoolExpr::Or(Box::new(self), Box::new(other))
}
#[allow(clippy::should_implement_trait)]
pub fn not(self) -> Self {
BoolExpr::Not(Box::new(self))
}
pub fn total_param_count(&self) -> usize {
match self {
BoolExpr::Filter(f) => f.param_count(),
BoolExpr::Raw(_) => 0,
BoolExpr::And(a, b) | BoolExpr::Or(a, b) => {
a.total_param_count() + b.total_param_count()
}
BoolExpr::Not(inner) => inner.total_param_count(),
BoolExpr::Exists(spec) | BoolExpr::NotExists(spec) => spec
.predicate
.as_ref()
.map(|p| p.total_param_count())
.unwrap_or(0),
BoolExpr::InSubquery(spec) | BoolExpr::NotInSubquery(spec) => spec
.predicate
.as_ref()
.map(|p| p.total_param_count())
.unwrap_or(0),
}
}
}
#[derive(Debug, Clone)]
pub struct OrderBy {
column: String,
pub(crate) direction: OrderDirection,
}
#[derive(Debug, Clone, Copy)]
pub enum OrderDirection {
Ascending,
Descending,
}
impl OrderBy {
pub fn new(column: impl Into<String>, direction: OrderDirection) -> Self {
Self {
column: column.into(),
direction,
}
}
pub fn to_sql(&self) -> String {
let dir = match self.direction {
OrderDirection::Ascending => "ASC",
OrderDirection::Descending => "DESC",
};
format!("{} {}", self.column, dir)
}
}
#[derive(Debug, Clone)]
pub struct CompiledFilter {
pub expr: BoolExpr,
pub params: Vec<DbValue>,
}
impl CompiledFilter {
pub fn new(expr: BoolExpr) -> Self {
let params = collect_bool_expr_values(&expr);
Self { expr, params }
}
}
#[derive(Debug, Clone)]
pub struct IncludePath {
pub navigation: String,
pub nested: Vec<IncludePath>,
pub related_table: Option<String>,
pub foreign_key_column: Option<String>,
pub referenced_key_column: Option<String>,
}
#[derive(Debug, Clone)]
pub struct JoinSpec {
pub join_type: String,
pub table: String,
pub on_clause: String,
}
impl JoinSpec {
pub fn to_sql(&self) -> String {
if self.join_type == "CROSS" {
format!("CROSS JOIN {}", self.table)
} else {
format!(
"{} JOIN {} ON {}",
self.join_type, self.table, self.on_clause
)
}
}
}
#[derive(Debug, Clone)]
pub struct GroupBy {
pub columns: Vec<String>,
}
impl GroupBy {
pub fn to_sql(&self) -> String {
if self.columns.is_empty() {
String::new()
} else {
format!("GROUP BY {}", self.columns.join(", "))
}
}
}
#[derive(Debug, Clone)]
pub struct HavingCondition {
pub expression: String,
}
impl HavingCondition {
pub fn to_sql(&self) -> String {
format!("HAVING {}", self.expression)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AggKind {
Count,
Sum,
Avg,
Min,
Max,
}
impl AggKind {
pub fn sql_name(&self) -> &'static str {
match self {
AggKind::Count => "COUNT",
AggKind::Sum => "SUM",
AggKind::Avg => "AVG",
AggKind::Min => "MIN",
AggKind::Max => "MAX",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name.to_uppercase().as_str() {
"COUNT" => Some(AggKind::Count),
"SUM" => Some(AggKind::Sum),
"AVG" => Some(AggKind::Avg),
"MIN" => Some(AggKind::Min),
"MAX" => Some(AggKind::Max),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Eq,
Ne,
Gt,
Ge,
Lt,
Le,
}
impl CompareOp {
pub fn sql_name(&self) -> &'static str {
match self {
CompareOp::Eq => "=",
CompareOp::Ne => "!=",
CompareOp::Gt => ">",
CompareOp::Ge => ">=",
CompareOp::Lt => "<",
CompareOp::Le => "<=",
}
}
pub fn from_symbol(symbol: &str) -> Option<Self> {
match symbol {
"=" => Some(CompareOp::Eq),
"!=" => Some(CompareOp::Ne),
">" => Some(CompareOp::Gt),
">=" => Some(CompareOp::Ge),
"<" => Some(CompareOp::Lt),
"<=" => Some(CompareOp::Le),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub enum HavingExpr {
Compare {
agg: AggKind,
col: String,
op: CompareOp,
value: DbValue,
},
And(Box<HavingExpr>, Box<HavingExpr>),
Or(Box<HavingExpr>, Box<HavingExpr>),
Not(Box<HavingExpr>),
CompareAgg {
left_agg: AggKind,
left_col: String,
op: CompareOp,
right_agg: AggKind,
right_col: String,
},
}
impl HavingExpr {
pub fn to_sql(
&self,
gen: &dyn crate::provider::ISqlGenerator,
param_idx: &mut usize,
) -> String {
match self {
Self::Compare {
agg,
col,
op,
value: _,
} => {
let placeholder = gen.parameter_placeholder(*param_idx);
*param_idx += 1;
format!(
"{}({}) {} {}",
agg.sql_name(),
col,
op.sql_name(),
placeholder
)
}
Self::And(left, right) => format!(
"({} AND {})",
left.to_sql(gen, param_idx),
right.to_sql(gen, param_idx)
),
Self::Or(left, right) => format!(
"({} OR {})",
left.to_sql(gen, param_idx),
right.to_sql(gen, param_idx)
),
Self::Not(inner) => format!("NOT ({})", inner.to_sql(gen, param_idx)),
Self::CompareAgg {
left_agg,
left_col,
op,
right_agg,
right_col,
} => format!(
"{}({}) {} {}({})",
left_agg.sql_name(),
left_col,
op.sql_name(),
right_agg.sql_name(),
right_col
),
}
}
pub fn collect_params(&self) -> Vec<DbValue> {
match self {
Self::Compare { value, .. } => vec![value.clone()],
Self::And(left, right) | Self::Or(left, right) => {
let mut v = left.collect_params();
v.extend(right.collect_params());
v
}
Self::Not(inner) => inner.collect_params(),
Self::CompareAgg { .. } => Vec::new(),
}
}
}