use super::*;
use inillucent_value::Affinity;
pub(super) fn comparison_collation(term: &BoundExpr) -> Collation {
match term {
BoundExpr::Compare { collation, .. } => *collation,
_ => Collation::Binary,
}
}
pub(super) fn collation_of(name: &[u8]) -> Collation {
Collation::from_name(core::str::from_utf8(name).unwrap_or("BINARY"))
.unwrap_or(Collation::Binary)
}
pub(super) fn comparison_against_column(
position: usize,
column: u16,
term: &BoundExpr,
) -> Option<(BinaryOp, BoundExpr)> {
let BoundExpr::Compare {
op, left, right, ..
} = term
else {
return None;
};
if compares_with_null(left, right) {
return None;
}
if let BoundExpr::Column {
source,
column: candidate,
..
} = left.as_ref()
{
if *source == position && *candidate == column {
return Some((*op, right.as_ref().clone()));
}
}
if let BoundExpr::Column {
source,
column: candidate,
..
} = right.as_ref()
{
if *source == position && *candidate == column {
return Some((mirror(*op), left.as_ref().clone()));
}
}
None
}
pub(super) fn indexable_comparison(
position: usize,
column: u16,
term: &BoundExpr,
) -> Option<(BinaryOp, BoundExpr)> {
let found = comparison_against_column(position, column, term)?;
let BoundExpr::Compare {
left,
right,
affinity,
..
} = term
else {
return None;
};
let column_affinity =
[left.as_ref(), right.as_ref()]
.into_iter()
.find_map(|side| match side {
BoundExpr::Column {
source,
column: candidate,
affinity,
..
} if *source == position && *candidate == column => Some(*affinity),
_ => None,
})?;
index_affinity_ok(*affinity, column_affinity).then_some(found)
}
pub(super) fn index_affinity_ok(comparison: Option<Affinity>, column: Affinity) -> bool {
match comparison {
None | Some(Affinity::Blob) => true,
Some(Affinity::Text) => column == Affinity::Text,
Some(_) => column.is_numeric(),
}
}
pub(super) fn compares_unconverted(term: &BoundExpr) -> bool {
matches!(term, BoundExpr::Compare { affinity: None, .. })
}
pub(super) fn comparison_against_rowid(
position: usize,
term: &BoundExpr,
) -> Option<(BinaryOp, BoundExpr)> {
let BoundExpr::Compare {
op, left, right, ..
} = term
else {
return None;
};
if compares_with_null(left, right) {
return None;
}
if matches!(left.as_ref(), BoundExpr::Rowid { source } if *source == position) {
return Some((*op, right.as_ref().clone()));
}
if matches!(right.as_ref(), BoundExpr::Rowid { source } if *source == position) {
return Some((mirror(*op), left.as_ref().clone()));
}
None
}
fn mirror(op: BinaryOp) -> BinaryOp {
match op {
BinaryOp::Less => BinaryOp::Greater,
BinaryOp::LessEqual => BinaryOp::GreaterEqual,
BinaryOp::Greater => BinaryOp::Less,
BinaryOp::GreaterEqual => BinaryOp::LessEqual,
other => other,
}
}
fn compares_with_null(left: &BoundExpr, right: &BoundExpr) -> bool {
matches!(left, BoundExpr::Null) || matches!(right, BoundExpr::Null)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_index_answers_only_a_comparison_its_affinity_agrees_with() {
for column in [
Affinity::Blob,
Affinity::Text,
Affinity::Numeric,
Affinity::Integer,
Affinity::Real,
] {
assert!(index_affinity_ok(None, column));
assert!(index_affinity_ok(Some(Affinity::Blob), column));
}
assert!(index_affinity_ok(Some(Affinity::Text), Affinity::Text));
assert!(!index_affinity_ok(Some(Affinity::Text), Affinity::Blob));
assert!(!index_affinity_ok(Some(Affinity::Text), Affinity::Integer));
assert!(index_affinity_ok(Some(Affinity::Numeric), Affinity::Real));
assert!(!index_affinity_ok(Some(Affinity::Numeric), Affinity::Blob));
assert!(!index_affinity_ok(Some(Affinity::Numeric), Affinity::Text));
}
#[test]
fn mirroring_preserves_meaning() {
assert_eq!(mirror(BinaryOp::Less), BinaryOp::Greater);
assert_eq!(mirror(BinaryOp::GreaterEqual), BinaryOp::LessEqual);
assert_eq!(mirror(BinaryOp::Equal), BinaryOp::Equal);
}
}