use crate::{
db::{
numeric::{compare_numeric_eq, compare_numeric_order},
predicate::{
CompareOp,
coercion::{CoercionId, CoercionSpec},
},
},
value::{TextMode, Value},
};
use std::{cmp::Ordering, mem::discriminant};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum TextOp {
StartsWith,
EndsWith,
}
#[must_use]
pub(in crate::db) fn compare_eq(
left: &Value,
right: &Value,
coercion: &CoercionSpec,
) -> Option<bool> {
match coercion.id {
CoercionId::Strict | CoercionId::CollectionElement => {
same_variant(left, right).then_some(left == right)
}
CoercionId::NumericWiden => compare_numeric_eq(left, right),
CoercionId::TextCasefold => compare_casefold(left, right),
}
}
#[must_use]
pub(in crate::db::predicate) const fn eval_equality_compare_result(
op: CompareOp,
matched: Option<bool>,
) -> bool {
match op {
CompareOp::Eq => matches!(matched, Some(true)),
CompareOp::Ne => matches!(matched, Some(false)),
CompareOp::Lt
| CompareOp::Lte
| CompareOp::Gt
| CompareOp::Gte
| CompareOp::In
| CompareOp::NotIn
| CompareOp::Contains
| CompareOp::StartsWith
| CompareOp::EndsWith => false,
}
}
#[must_use]
pub(in crate::db) fn compare_order(
left: &Value,
right: &Value,
coercion: &CoercionSpec,
) -> Option<Ordering> {
match coercion.id {
CoercionId::Strict | CoercionId::CollectionElement => {
if !same_variant(left, right) {
return None;
}
Value::strict_order_cmp(left, right)
}
CoercionId::NumericWiden => compare_numeric_order(left, right),
CoercionId::TextCasefold => {
let left = casefold_value(left)?;
let right = casefold_value(right)?;
Some(left.cmp(&right))
}
}
}
#[must_use]
pub(in crate::db::predicate) const fn eval_ordered_compare_result(
op: CompareOp,
ordering: Ordering,
) -> bool {
match op {
CompareOp::Eq => matches!(ordering, Ordering::Equal),
CompareOp::Ne => !matches!(ordering, Ordering::Equal),
CompareOp::Lt => ordering.is_lt(),
CompareOp::Lte => ordering.is_le(),
CompareOp::Gt => ordering.is_gt(),
CompareOp::Gte => ordering.is_ge(),
CompareOp::In
| CompareOp::NotIn
| CompareOp::Contains
| CompareOp::StartsWith
| CompareOp::EndsWith => false,
}
}
#[must_use]
pub(in crate::db::predicate) const fn eval_list_membership_compare_result(
op: CompareOp,
matched: Option<bool>,
) -> bool {
match op {
CompareOp::In => matches!(matched, Some(true)),
CompareOp::NotIn => matches!(matched, Some(false)),
CompareOp::Eq
| CompareOp::Ne
| CompareOp::Lt
| CompareOp::Lte
| CompareOp::Gt
| CompareOp::Gte
| CompareOp::Contains
| CompareOp::StartsWith
| CompareOp::EndsWith => false,
}
}
#[must_use]
pub(in crate::db) fn canonical_cmp(left: &Value, right: &Value) -> Ordering {
if let Some(ordering) = Value::strict_order_cmp(left, right) {
return ordering;
}
left.canonical_rank().cmp(&right.canonical_rank())
}
#[must_use]
pub(in crate::db) fn compare_text(
left: &Value,
right: &Value,
coercion: &CoercionSpec,
op: TextOp,
) -> Option<bool> {
if !matches!(left, Value::Text(_)) || !matches!(right, Value::Text(_)) {
return None;
}
let mode = match coercion.id {
CoercionId::Strict => TextMode::Cs,
CoercionId::TextCasefold => TextMode::Ci,
_ => return None,
};
match op {
TextOp::StartsWith => left.text_starts_with(right, mode),
TextOp::EndsWith => left.text_ends_with(right, mode),
}
}
fn same_variant(left: &Value, right: &Value) -> bool {
discriminant(left) == discriminant(right)
}
fn compare_casefold(left: &Value, right: &Value) -> Option<bool> {
let left = casefold_value(left)?;
let right = casefold_value(right)?;
Some(left == right)
}
fn casefold_value(value: &Value) -> Option<String> {
match value {
Value::Text(text) => Some(casefold_text(text)),
_ => None,
}
}
#[must_use]
pub(in crate::db::predicate) fn casefold_text(input: &str) -> String {
if input.is_ascii() {
return input.to_ascii_lowercase();
}
input.to_lowercase()
}