in crates/iceberg/src/expr/visitors/page_index_evaluator.rs [649:722]
fn not_starts_with(
&mut self,
reference: &BoundReference,
datum: &Datum,
_predicate: &BoundPredicate,
) -> Result<RowSelection> {
let field_id = reference.field().id;
// notStartsWith will match unless all values must start with the prefix.
// This happens when the lower and upper bounds both start with the prefix.
let PrimitiveLiteral::String(prefix) = datum.literal() else {
return Err(Error::new(
ErrorKind::Unexpected,
"Cannot use StartsWith operator on non-string values",
));
};
self.calc_row_selection(
field_id,
|min, max, nulls| {
if !matches!(nulls, PageNullCount::NoneNull) {
return Ok(true);
}
let Some(lower_bound) = min else {
return Ok(true);
};
let PrimitiveLiteral::String(lower_bound_str) = lower_bound.literal() else {
return Err(Error::new(
ErrorKind::Unexpected,
"Cannot use NotStartsWith operator on non-string lower_bound value",
));
};
if lower_bound_str < prefix {
// if lower is shorter than the prefix then lower doesn't start with the prefix
return Ok(true);
}
let prefix_len = prefix.chars().count();
if lower_bound_str.chars().take(prefix_len).collect::<String>() == *prefix {
// lower bound matches the prefix
let Some(upper_bound) = max else {
return Ok(true);
};
let PrimitiveLiteral::String(upper_bound) = upper_bound.literal() else {
return Err(Error::new(
ErrorKind::Unexpected,
"Cannot use NotStartsWith operator on non-string upper_bound value",
));
};
// if upper is shorter than the prefix then upper can't start with the prefix
if upper_bound.chars().count() < prefix_len {
return Ok(true);
}
if upper_bound.chars().take(prefix_len).collect::<String>() == *prefix {
// both bounds match the prefix, so all rows must match the
// prefix and therefore do not satisfy the predicate
return Ok(false);
}
}
Ok(true)
},
MissingColBehavior::MightMatch,
)
}