use crate::{
db::query::plan::expr::{
BinaryOp, CaseWhenArm, Expr, Function, UnaryOp,
canonicalize::{
normalize_bool_expr,
truth_admission::{TruthAdmission, TruthWrapperScope},
},
},
value::Value,
};
const MAX_BOOL_CASE_CANONICALIZATION_ARMS: usize = 8;
pub(super) fn normalize_bool_case_expr(
when_then_arms: Vec<CaseWhenArm>,
else_expr: Expr,
top_level_where_null_collapse: bool,
) -> Expr {
lower_searched_case_to_boolean(
when_then_arms.as_slice(),
&else_expr,
top_level_where_null_collapse,
)
.unwrap_or_else(|| Expr::Case {
when_then_arms,
else_expr: Box::new(else_expr),
})
}
pub(super) fn canonicalize_normalized_bool_case_in_bool_context(
expr: Expr,
top_level_where_null_collapse: bool,
truth_wrapper_scope: Option<TruthWrapperScope>,
) -> Expr {
match expr {
Expr::Unary {
op: UnaryOp::Not,
expr,
} => Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(canonicalize_normalized_bool_case_in_bool_context(
*expr,
false,
truth_wrapper_scope,
)),
},
Expr::Binary {
op: logical @ (BinaryOp::And | BinaryOp::Or),
left,
right,
} => Expr::Binary {
op: logical,
left: Box::new(canonicalize_normalized_bool_case_in_bool_context(
*left,
top_level_where_null_collapse,
truth_wrapper_scope,
)),
right: Box::new(canonicalize_normalized_bool_case_in_bool_context(
*right,
top_level_where_null_collapse,
truth_wrapper_scope,
)),
},
Expr::Case {
when_then_arms,
else_expr,
} => {
let when_then_arms = when_then_arms
.into_iter()
.map(|arm| {
CaseWhenArm::new(
canonicalize_normalized_bool_case_in_bool_context(
arm.condition().clone(),
true,
truth_wrapper_scope,
),
canonicalize_normalized_bool_case_in_bool_context(
arm.result().clone(),
top_level_where_null_collapse,
truth_wrapper_scope,
),
)
})
.collect::<Vec<_>>();
let else_expr = canonicalize_normalized_bool_case_in_bool_context(
*else_expr,
top_level_where_null_collapse,
truth_wrapper_scope,
);
normalize_bool_case_expr(when_then_arms, else_expr, top_level_where_null_collapse)
}
other => maybe_collapse_truth_wrapper_in_bool_context(other, truth_wrapper_scope),
}
}
fn maybe_collapse_truth_wrapper_in_bool_context(
expr: Expr,
scope: Option<TruthWrapperScope>,
) -> Expr {
let Some(scope) = scope else {
return expr;
};
match expr {
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} if matches!(right.as_ref(), Expr::Literal(Value::Bool(true)))
&& truth_wrapper_candidate(left.as_ref(), scope) =>
{
*left
}
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} if matches!(left.as_ref(), Expr::Literal(Value::Bool(true)))
&& truth_wrapper_candidate(right.as_ref(), scope) =>
{
*right
}
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} if matches!(right.as_ref(), Expr::Literal(Value::Bool(false)))
&& truth_wrapper_candidate(left.as_ref(), scope) =>
{
Expr::Unary {
op: UnaryOp::Not,
expr: left,
}
}
Expr::Binary {
op: BinaryOp::Eq,
left,
right,
} if matches!(left.as_ref(), Expr::Literal(Value::Bool(false)))
&& truth_wrapper_candidate(right.as_ref(), scope) =>
{
Expr::Unary {
op: UnaryOp::Not,
expr: right,
}
}
other => other,
}
}
fn truth_wrapper_candidate(expr: &Expr, scope: TruthWrapperScope) -> bool {
match scope {
TruthWrapperScope::ScalarWhere => TruthAdmission::is_scalar_condition(expr),
TruthWrapperScope::GroupedHaving => TruthAdmission::is_grouped_condition(expr),
}
}
fn lower_searched_case_to_boolean(
arms: &[CaseWhenArm],
else_expr: &Expr,
top_level_where_null_collapse: bool,
) -> Option<Expr> {
if arms.is_empty() || arms.len() > MAX_BOOL_CASE_CANONICALIZATION_ARMS {
return None;
}
let mut canonical = match (top_level_where_null_collapse, else_expr) {
(true, Expr::Literal(Value::Null)) => Expr::Literal(Value::Bool(false)),
(_, other) => other.clone(),
};
for arm in arms.iter().rev() {
canonical = normalize_bool_expr(Expr::Binary {
op: BinaryOp::Or,
left: Box::new(guarded_bool_case_branch(
searched_case_match_guard(arm.condition().clone()),
arm.result().clone(),
)),
right: Box::new(guarded_bool_case_branch(
Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(searched_case_match_guard(arm.condition().clone())),
},
canonical,
)),
});
}
Some(canonical)
}
fn guarded_bool_case_branch(guard: Expr, result: Expr) -> Expr {
match result {
Expr::Literal(Value::Bool(true)) => guard,
Expr::Literal(Value::Bool(false)) => Expr::Literal(Value::Bool(false)),
other => Expr::Binary {
op: BinaryOp::And,
left: Box::new(guard),
right: Box::new(other),
},
}
}
fn searched_case_match_guard(condition: Expr) -> Expr {
Expr::FunctionCall {
function: Function::Coalesce,
args: vec![condition, Expr::Literal(Value::Bool(false))],
}
}