use std::collections::HashSet;
use super::literals::try_literal_to_value;
use crate::catalog::Distance;
use crate::exec::index::analysis::idiom_matches_containment;
use crate::expr::operator::NearestNeighbor;
use crate::expr::visit::{MutVisitor, Visit, VisitMut, Visitor};
use crate::expr::{BinaryOperator, Cond, Expr, Idiom, Literal};
use crate::val::Number;
pub(crate) fn has_top_level_or(cond: Option<&Cond>) -> bool {
match cond {
Some(c) => matches!(
c.0,
Expr::Binary {
op: BinaryOperator::Or,
..
}
),
None => false,
}
}
pub(crate) fn has_knn_operator(expr: &Expr) -> bool {
scan_knn_operators(expr).found_any
}
pub(crate) fn has_knn_k_operator(expr: &Expr) -> bool {
scan_knn_operators(expr).found_k
}
pub(crate) fn has_knn_ktree_operator(expr: &Expr) -> bool {
scan_knn_operators(expr).found_ktree
}
fn scan_knn_operators(expr: &Expr) -> KnnOperatorChecker {
let mut checker = KnnOperatorChecker {
found_any: false,
found_k: false,
found_ktree: false,
};
let _ = checker.visit_expr(expr);
checker
}
struct KnnOperatorChecker {
found_any: bool,
found_k: bool,
found_ktree: bool,
}
impl Visitor for KnnOperatorChecker {
type Error = std::convert::Infallible;
fn visit_expr(&mut self, expr: &Expr) -> Result<(), Self::Error> {
if let Expr::Binary {
op: BinaryOperator::NearestNeighbor(nn),
..
} = expr
{
self.found_any = true;
match nn.as_ref() {
NearestNeighbor::K(..) => self.found_k = true,
NearestNeighbor::KTree(..) => self.found_ktree = true,
NearestNeighbor::Approximate(..) => {}
}
}
expr.visit(self)
}
fn visit_select(&mut self, _: &crate::expr::SelectStatement) -> Result<(), Self::Error> {
Ok(())
}
}
pub(crate) struct BruteForceKnnParams {
pub field: Idiom,
pub vector: Vec<Number>,
pub k: u32,
pub distance: Distance,
}
pub(crate) fn extract_bruteforce_knn(cond: &Cond) -> Option<BruteForceKnnParams> {
let mut expr = cond.0.clone();
let mut extractor = BruteForceKnnExtractor {
params: None,
};
let _ = extractor.visit_mut_expr(&mut expr);
extractor.params
}
pub(crate) fn strip_fts_condition(cond: &Cond) -> Option<Cond> {
strip_and_simplify(cond.0.clone(), |e| {
matches!(
e,
Expr::Binary {
op: BinaryOperator::Matches(_),
..
}
)
})
.map(Cond)
}
pub(crate) fn strip_knn_from_condition(cond: &Cond) -> Option<Cond> {
strip_and_simplify(cond.0.clone(), |e| {
matches!(
e,
Expr::Binary {
op: BinaryOperator::NearestNeighbor(nn),
..
} if matches!(nn.as_ref(), NearestNeighbor::K(..) | NearestNeighbor::Approximate(..))
)
})
.map(Cond)
}
fn strip_and_simplify<F>(mut expr: Expr, mut should_strip: F) -> Option<Expr>
where
F: FnMut(&Expr) -> bool,
{
fn walk<F: FnMut(&Expr) -> bool>(expr: &mut Expr, f: &mut F) {
if let Expr::Binary {
left,
op: BinaryOperator::And,
right,
} = expr
{
walk(left, f);
walk(right, f);
} else if f(expr) {
*expr = Expr::Literal(Literal::Bool(true));
}
}
walk(&mut expr, &mut should_strip);
let _ = BoolSimplifier.visit_mut_expr(&mut expr);
if matches!(expr, Expr::Literal(Literal::Bool(true))) {
None
} else {
Some(expr)
}
}
pub(crate) fn strip_index_conditions(
cond: &Cond,
access: &crate::exec::index::access_path::BTreeAccess,
cols: &[Idiom],
) -> Option<Cond> {
let matcher = IndexConditionMatcher {
cols,
access,
};
strip_and_simplify(cond.0.clone(), |e| match e {
Expr::Binary {
left,
op,
right,
} => matcher.matches_access(left, op, right),
_ => false,
})
.map(Cond)
}
pub(crate) fn strip_union_index_conditions(
cond: &Cond,
paths: &[crate::exec::index::access_path::AccessPath],
) -> Option<Cond> {
use crate::exec::index::access_path::{AccessPath, BTreeAccess};
use crate::val::Value;
let mut first_col: Option<&Idiom> = None;
let mut branch_values: HashSet<&Value> = HashSet::with_capacity(paths.len());
for path in paths {
let AccessPath::BTreeScan {
index_ref,
access,
..
} = path
else {
return Some(cond.clone());
};
let col = index_ref.definition().cols.first()?;
match first_col {
None => first_col = Some(col),
Some(prev) if prev == col => {}
Some(_) => return Some(cond.clone()),
}
match access {
BTreeAccess::Equality(v) => {
branch_values.insert(v);
}
BTreeAccess::Compound {
prefix,
range: None,
} if prefix.len() == 1 => {
branch_values.insert(&prefix[0]);
}
_ => return Some(cond.clone()),
}
}
let Some(col) = first_col else {
return Some(cond.clone());
};
strip_and_simplify(cond.0.clone(), |e| match e {
Expr::Binary {
left,
op,
right,
} => union_covers_leaf(col, &branch_values, left, op, right),
_ => false,
})
.map(Cond)
}
fn union_covers_leaf(
col: &Idiom,
branch_values: &HashSet<&crate::val::Value>,
left: &Expr,
op: &BinaryOperator,
right: &Expr,
) -> bool {
use crate::exec::index::analysis::idiom_matches_containment;
use crate::val::Value;
let (idiom, lit) = match op {
BinaryOperator::ContainAny => match (left, right) {
(Expr::Idiom(i), Expr::Literal(l)) => (i, l),
_ => return false,
},
BinaryOperator::AnyInside => match (left, right) {
(Expr::Literal(l), Expr::Idiom(i)) => (i, l),
_ => return false,
},
_ => return false,
};
if !idiom_matches_containment(idiom, col) {
return false;
}
let Some(Value::Array(arr)) = try_literal_to_value(lit) else {
return false;
};
if arr.0.is_empty() {
return false;
}
arr.0.iter().all(|v| branch_values.contains(v))
}
struct IndexConditionMatcher<'a> {
cols: &'a [Idiom],
access: &'a crate::exec::index::access_path::BTreeAccess,
}
impl IndexConditionMatcher<'_> {
fn matches_access(&self, left: &Expr, op: &BinaryOperator, right: &Expr) -> bool {
use crate::exec::index::access_path::BTreeAccess;
let (idiom, value, effective_op, idiom_on_left) = match (left, right) {
(Expr::Idiom(i), Expr::Literal(lit)) => {
if let Some(v) = try_literal_to_value(lit) {
(i, v, op.clone(), true)
} else {
return false;
}
}
(Expr::Literal(lit), Expr::Idiom(i)) => {
if let Some(v) = try_literal_to_value(lit) {
let flipped = match op {
BinaryOperator::LessThan => BinaryOperator::MoreThan,
BinaryOperator::LessThanEqual => BinaryOperator::MoreThanEqual,
BinaryOperator::MoreThan => BinaryOperator::LessThan,
BinaryOperator::MoreThanEqual => BinaryOperator::LessThanEqual,
other => other.clone(),
};
(i, v, flipped, false)
} else {
return false;
}
}
_ => return false,
};
use crate::val::Value;
let (effective_op, value) = if idiom_on_left
&& matches!(effective_op, BinaryOperator::Inside)
&& let Value::Array(arr) = &value
&& arr.len() == 1
{
(BinaryOperator::Equal, arr[0].clone())
} else {
(effective_op, value)
};
let is_equality =
matches!(effective_op, BinaryOperator::Equal | BinaryOperator::ExactEqual);
let is_containment = (matches!(effective_op, BinaryOperator::Contain) && idiom_on_left)
|| (matches!(effective_op, BinaryOperator::Inside) && !idiom_on_left);
match self.access {
BTreeAccess::Compound {
prefix,
range,
} => {
if is_equality {
for (col, val) in self.cols.iter().zip(prefix.iter()) {
if idiom == col && value == *val {
return true;
}
}
}
if is_containment
&& let Some(col) = self.cols.first()
&& let Some(val) = prefix.first()
&& idiom_matches_containment(idiom, col)
&& value == *val
{
return true;
}
if let Some((range_op, range_val)) = range
&& let Some(col) = self.cols.get(prefix.len())
&& idiom == col
{
if effective_op == *range_op && value == *range_val {
return true;
}
if matches!(range_op, BinaryOperator::MoreThan)
&& matches!(range_val, Value::None)
&& effective_op == BinaryOperator::NotEqual
&& matches!(value, Value::None)
{
return true;
}
}
false
}
BTreeAccess::Equality(val) => {
let Some(col) = self.cols.first() else {
return false;
};
if is_equality && idiom == col && value == *val {
return true;
}
if is_containment && idiom_matches_containment(idiom, col) && value == *val {
return true;
}
false
}
BTreeAccess::Range {
from,
to,
} => {
let Some(col) = self.cols.first() else {
return false;
};
if idiom != col {
return false;
}
if let Some(from) = from {
let expected_op = if from.inclusive {
BinaryOperator::MoreThanEqual
} else {
BinaryOperator::MoreThan
};
if effective_op == expected_op && value == from.value {
return true;
}
if !from.inclusive
&& matches!(from.value, Value::None)
&& effective_op == BinaryOperator::NotEqual
&& value == from.value
{
return true;
}
}
if let Some(to) = to {
let expected_op = if to.inclusive {
BinaryOperator::LessThanEqual
} else {
BinaryOperator::LessThan
};
if effective_op == expected_op && value == to.value {
return true;
}
}
false
}
_ => false,
}
}
}
struct BruteForceKnnExtractor {
params: Option<BruteForceKnnParams>,
}
impl MutVisitor for BruteForceKnnExtractor {
type Error = std::convert::Infallible;
fn visit_mut_expr(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
if self.params.is_some() {
return Ok(());
}
if let Expr::Binary {
left,
op: BinaryOperator::NearestNeighbor(nn),
right,
} = expr && let NearestNeighbor::K(k, dist) = nn.as_ref()
&& let Expr::Idiom(idiom) = left.as_ref()
&& let Some(vector) = extract_literal_vector(right)
{
self.params = Some(BruteForceKnnParams {
field: idiom.clone(),
vector,
k: *k,
distance: dist.clone(),
});
*expr = Expr::Literal(Literal::Bool(true));
return Ok(());
}
expr.visit_mut(self)
}
fn visit_mut_select(
&mut self,
_: &mut crate::expr::SelectStatement,
) -> Result<(), Self::Error> {
Ok(())
}
}
struct BoolSimplifier;
impl MutVisitor for BoolSimplifier {
type Error = std::convert::Infallible;
fn visit_mut_expr(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
expr.visit_mut(self)?;
if let Expr::Binary {
left,
op: BinaryOperator::And,
right,
} = expr
{
let l_true = matches!(left.as_ref(), Expr::Literal(Literal::Bool(true)));
let r_true = matches!(right.as_ref(), Expr::Literal(Literal::Bool(true)));
match (l_true, r_true) {
(true, true) => *expr = Expr::Literal(Literal::Bool(true)),
(true, false) => {
let r = std::mem::replace(right.as_mut(), Expr::Literal(Literal::None));
*expr = r;
}
(false, true) => {
let l = std::mem::replace(left.as_mut(), Expr::Literal(Literal::None));
*expr = l;
}
_ => {}
}
}
Ok(())
}
fn visit_mut_select(
&mut self,
_: &mut crate::expr::SelectStatement,
) -> Result<(), Self::Error> {
Ok(())
}
}
fn extract_literal_vector(expr: &Expr) -> Option<Vec<Number>> {
match expr {
Expr::Literal(lit) => {
if let Literal::Array(arr) = lit {
let mut nums = Vec::with_capacity(arr.len());
for elem in arr.iter() {
match elem {
Expr::Literal(Literal::Integer(i)) => {
nums.push(Number::Int(*i));
}
Expr::Literal(Literal::Float(f)) => {
nums.push(Number::Float(*f));
}
Expr::Literal(Literal::Decimal(d)) => {
nums.push(Number::Decimal(*d));
}
_ => return None,
}
}
Some(nums)
} else {
None
}
}
_ => None,
}
}
pub(crate) fn extract_record_id_point_lookup(
cond: &Cond,
table_name: &crate::val::TableName,
) -> Option<Expr> {
find_id_equality_in_and_chain(&cond.0, table_name)
}
fn find_id_equality_in_and_chain(expr: &Expr, table_name: &crate::val::TableName) -> Option<Expr> {
match expr {
Expr::Binary {
left,
op: BinaryOperator::And,
right,
} => find_id_equality_in_and_chain(left, table_name)
.or_else(|| find_id_equality_in_and_chain(right, table_name)),
Expr::Binary {
left,
op: BinaryOperator::Equal | BinaryOperator::ExactEqual,
right,
} => check_id_recordid_pair(left, right, table_name)
.or_else(|| check_id_recordid_pair(right, left, table_name)),
_ => None,
}
}
fn check_id_recordid_pair(
idiom_side: &Expr,
lit_side: &Expr,
table_name: &crate::val::TableName,
) -> Option<Expr> {
if let Expr::Idiom(idiom) = idiom_side
&& idiom.is_id()
&& let Expr::Literal(Literal::RecordId(rid)) = lit_side
&& &rid.table == table_name
&& !matches!(rid.key, crate::expr::RecordIdKeyLit::Range(_))
{
Some(lit_side.clone())
} else {
None
}
}
pub(crate) fn is_value_source_expr(expr: &Expr) -> bool {
match expr {
Expr::Literal(Literal::Array(_)) => true,
Expr::Literal(Literal::String(_))
| Expr::Literal(Literal::Integer(_))
| Expr::Literal(Literal::Float(_))
| Expr::Literal(Literal::Decimal(_))
| Expr::Literal(Literal::Bool(_))
| Expr::Literal(Literal::None)
| Expr::Literal(Literal::Null) => true,
Expr::Table(_) => false,
Expr::Literal(Literal::RecordId(_)) => false,
Expr::Param(_) => false,
Expr::Select(_) => false,
_ => false,
}
}
pub(crate) fn all_value_sources(sources: &[Expr]) -> bool {
!sources.is_empty() && sources.iter().all(is_value_source_expr)
}
pub(crate) fn extract_matches_context(
cond: &Cond,
ctx: Option<&crate::ctx::FrozenContext>,
) -> crate::exec::function::MatchesContext {
let mut collector = MatchesCollector(crate::exec::function::MatchesContext::new(), ctx);
let _ = collector.visit_expr(&cond.0);
collector.0
}
struct MatchesCollector<'a>(
crate::exec::function::MatchesContext,
Option<&'a crate::ctx::FrozenContext>,
);
impl Visitor for MatchesCollector<'_> {
type Error = std::convert::Infallible;
fn visit_expr(&mut self, expr: &Expr) -> Result<(), Self::Error> {
if let Expr::Binary {
left,
op: BinaryOperator::Matches(matches_op),
right,
} = expr && let Expr::Idiom(idiom) = left.as_ref()
{
let query_str = match right.as_ref() {
Expr::Literal(Literal::String(s)) => Some(s.as_str().to_owned()),
Expr::Param(param) => {
self.1.and_then(|ctx| {
ctx.value(param.as_str()).and_then(|v| {
if let crate::val::Value::String(s) = v {
Some(s.as_str().to_owned())
} else {
None
}
})
})
}
_ => None,
};
if let Some(query) = query_str {
let match_ref = matches_op.rf.unwrap_or(0);
self.0.insert(
match_ref,
crate::exec::function::MatchInfo {
idiom: idiom.clone(),
query,
},
);
}
}
expr.visit(self)
}
fn visit_select(&mut self, _: &crate::expr::SelectStatement) -> Result<(), Self::Error> {
Ok(())
}
}
pub(crate) fn extract_table_from_context(ctx: &crate::ctx::FrozenContext) -> crate::val::TableName {
if let Some(mc) = ctx.get_matches_context()
&& let Some(table) = mc.table()
{
return table.clone();
}
crate::val::TableName::from("unknown".to_string())
}