use super::*;
pub(super) fn pattern_range(
id: usize,
column: u16,
term: &BoundExpr,
collation: Collation,
descending: bool,
) -> Option<(Option<RangeBound>, Option<RangeBound>)> {
let BoundExpr::Pattern {
negated,
op,
operand,
pattern,
escape,
} = term
else {
return None;
};
if *negated || escape.is_some() {
return None;
}
let exact = match op {
crate::ast::PatternOp::Glob => true,
crate::ast::PatternOp::Like => false,
_ => return None,
};
match (exact, collation) {
(true, Collation::Binary) => {}
(false, Collation::NoCase) => {}
_ => return None,
}
let BoundExpr::Column {
source,
column: candidate,
..
} = operand.as_ref()
else {
return None;
};
if *source != id || *candidate != column {
return None;
}
let BoundExpr::Text(text) = pattern.as_ref() else {
return None;
};
let prefix = anchored_prefix(text, exact)?;
let low = RangeBound {
kind: BoundKind::GreaterEqual,
value: BoundExpr::Text(prefix.clone()),
unconverted: false,
};
let high = next_prefix(&prefix).map(|above| RangeBound {
kind: BoundKind::Less,
value: BoundExpr::Text(above),
unconverted: false,
});
match descending {
false => Some((Some(low), high)),
true => Some((
high.map(|bound| RangeBound {
kind: BoundKind::Greater,
value: bound.value,
unconverted: false,
}),
Some(RangeBound {
kind: BoundKind::LessEqual,
value: low.value,
unconverted: false,
}),
)),
}
}
fn anchored_prefix(pattern: &[u8], glob: bool) -> Option<Vec<u8>> {
let mut prefix = Vec::new();
for byte in pattern {
let wildcard = match glob {
true => matches!(byte, b'*' | b'?' | b'['),
false => matches!(byte, b'%' | b'_'),
};
if wildcard {
break;
}
prefix.push(*byte);
}
match prefix.is_empty() || prefix.len() == pattern.len() {
true => None,
false => Some(prefix),
}
}
fn next_prefix(prefix: &[u8]) -> Option<Vec<u8>> {
let mut above = prefix.to_vec();
while let Some(last) = above.pop() {
if last < 0xFF {
above.push(last.saturating_add(1));
return Some(above);
}
}
None
}