use super::*;
use crate::bind::SubqueryKind;
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)> {
if let BoundExpr::Compare { left, right, .. } = term {
if compares_with_null(left, right) {
return None;
}
}
comparison_against_column_even_if_null(position, column, term)
}
pub(super) fn comparison_against_column_even_if_null(
position: usize,
column: u16,
term: &BoundExpr,
) -> Option<(BinaryOp, BoundExpr)> {
let BoundExpr::Compare {
op, left, right, ..
} = term
else {
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)
}
pub(super) fn virtual_constraint(
id: usize,
table: &TableInfo,
term: &BoundExpr,
) -> Option<(i32, crate::vtab::ConstraintOp, BoundExpr)> {
use crate::vtab::{ConstraintOp, ROWID_COLUMN};
if let BoundExpr::Pattern {
negated: false,
op,
operand,
pattern,
escape: None,
} = term
{
if let BoundExpr::Column { source, column, .. } = operand.as_ref() {
if *source == id {
let op = match op {
crate::ast::PatternOp::Match => ConstraintOp::Match,
crate::ast::PatternOp::Like => ConstraintOp::Like,
crate::ast::PatternOp::Glob => ConstraintOp::Glob,
crate::ast::PatternOp::Regexp => ConstraintOp::Regexp,
};
return Some((i32::from(*column), op, pattern.as_ref().clone()));
}
}
}
if let Some((op, value)) = comparison_against_rowid(id, term) {
return binary_constraint(op).map(|op| (ROWID_COLUMN, op, value));
}
for column in 0..table.columns.len() {
let column = column as u16;
let hidden = table
.columns
.get(usize::from(column))
.is_some_and(|info| info.hidden);
let found = if hidden {
comparison_against_column_even_if_null(id, column, term)
} else {
comparison_against_column(id, column, term)
};
if let Some((op, value)) = found {
return binary_constraint(op).map(|op| (i32::from(column), op, value));
}
}
None
}
pub(super) fn constant_in_list(
id: usize,
table: &TableInfo,
term: &BoundExpr,
) -> Option<(i32, Vec<BoundExpr>)> {
let (operand, list) = match term {
BoundExpr::InList {
negated: false,
operand,
list,
..
} if !list.is_empty() => (operand, list.clone()),
BoundExpr::Subquery {
kind: SubqueryKind::In,
negated: false,
operand: Some(operand),
block,
..
} if block.correlations.is_empty() => (operand, vec![term.clone()]),
_ => return None,
};
let constant = |value: &BoundExpr| {
let mut used = Vec::new();
value.sources_used(&mut used);
used.is_empty()
};
let is_subquery = matches!(term, BoundExpr::Subquery { .. });
if !is_subquery && !list.iter().all(constant) {
return None;
}
match operand.as_ref() {
BoundExpr::Rowid { source } if *source == id => Some((crate::vtab::ROWID_COLUMN, list)),
BoundExpr::Column { source, column, .. }
if *source == id && table.column(*column).is_some_and(|held| held.hidden) =>
{
Some((i32::from(*column), list))
}
_ => None,
}
}
pub(super) fn binary_constraint(op: BinaryOp) -> Option<crate::vtab::ConstraintOp> {
use crate::vtab::ConstraintOp;
Some(match op {
BinaryOp::Equal => ConstraintOp::Eq,
BinaryOp::NotEqual => ConstraintOp::Ne,
BinaryOp::Less => ConstraintOp::Lt,
BinaryOp::LessEqual => ConstraintOp::Le,
BinaryOp::Greater => ConstraintOp::Gt,
BinaryOp::GreaterEqual => ConstraintOp::Ge,
_ => return None,
})
}
pub(super) fn reads_a_column(expr: &BoundExpr) -> bool {
if matches!(
expr,
BoundExpr::Column { .. } | BoundExpr::Rowid { .. } | BoundExpr::VirtualFunction { .. }
) {
return true;
}
expr.children().into_iter().any(reads_a_column)
}
#[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);
}
}