Files
Stalwart/crates/store/src/search/local.rs
2026-06-18 19:16:22 +02:00

229 lines
6.9 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::search::*;
use roaring::RoaringBitmap;
struct State {
pub op: SearchFilter,
pub bm: Option<RoaringBitmap>,
}
impl SearchQuery {
pub fn new(index: SearchIndex) -> Self {
Self {
index,
filters: Vec::new(),
comparators: Vec::new(),
mask: RoaringBitmap::new(),
}
}
pub fn with_filters(mut self, filters: Vec<SearchFilter>) -> Self {
if self.filters.is_empty() {
self.filters = filters;
} else {
self.filters.extend(filters);
}
self
}
pub fn with_comparators(mut self, comparators: Vec<SearchComparator>) -> Self {
if self.comparators.is_empty() {
self.comparators = comparators;
} else {
self.comparators.extend(comparators);
}
self
}
pub fn with_filter(mut self, filter: SearchFilter) -> Self {
self.filters.push(filter);
self
}
pub fn add_filter(&mut self, filter: SearchFilter) -> &mut Self {
self.filters.push(filter);
self
}
pub fn with_comparator(mut self, comparator: SearchComparator) -> Self {
self.comparators.push(comparator);
self
}
pub fn with_mask(mut self, mask: RoaringBitmap) -> Self {
self.mask = mask;
self
}
pub fn with_account_id(mut self, account_id: u32) -> Self {
self.filters.push(SearchFilter::cond(
SearchField::AccountId,
SearchOperator::Equal,
SearchValue::Uint(account_id as u64),
));
self
}
pub fn filter(self) -> QueryResults {
if self.filters.is_empty() {
return QueryResults {
results: self.mask,
comparators: self.comparators,
};
}
let mut state: State = State {
op: SearchFilter::And,
bm: None,
};
let mut stack = Vec::new();
let mut filters = self.filters.into_iter().peekable();
let mask = self.mask;
while let Some(filter) = filters.next() {
let mut result = match filter {
SearchFilter::DocumentSet(set) => Some(set),
op @ (SearchFilter::And | SearchFilter::Or | SearchFilter::Not) => {
stack.push(state);
state = State { op, bm: None };
continue;
}
SearchFilter::End => {
if let Some(prev_state) = stack.pop() {
let bm = state.bm;
state = prev_state;
bm
} else {
break;
}
}
SearchFilter::Operator { .. } => {
continue;
}
};
// Apply logical operation
if let Some(dest) = &mut state.bm {
match state.op {
SearchFilter::And => {
if let Some(result) = result {
dest.bitand_assign(result);
} else {
dest.clear();
}
}
SearchFilter::Or => {
if let Some(result) = result {
dest.bitor_assign(result);
}
}
SearchFilter::Not => {
if let Some(mut result) = result {
result.bitxor_assign(&mask);
dest.bitand_assign(result);
}
}
_ => unreachable!(),
}
} else if let Some(ref mut result_) = result {
if let SearchFilter::Not = state.op {
result_.bitxor_assign(&mask);
}
state.bm = result;
} else if let SearchFilter::Not = state.op {
state.bm = Some(mask.clone());
} else {
state.bm = Some(RoaringBitmap::new());
}
// And short-circuit
if matches!(state.op, SearchFilter::And) && state.bm.as_ref().unwrap().is_empty() {
while let Some(filter) = filters.peek() {
if matches!(filter, SearchFilter::End) {
break;
} else {
filters.next();
}
}
}
}
// AND with mask
let mut results = state.bm.unwrap_or_default();
results.bitand_assign(&mask);
QueryResults {
results,
comparators: self.comparators,
}
}
}
impl QueryResults {
pub fn new(results: RoaringBitmap, comparators: Vec<SearchComparator>) -> Self {
Self {
results,
comparators,
}
}
pub fn with_comparators(mut self, comparators: Vec<SearchComparator>) -> Self {
if self.comparators.is_empty() {
self.comparators = comparators;
} else {
self.comparators.extend(comparators);
}
self
}
pub fn results(&self) -> &RoaringBitmap {
&self.results
}
pub fn update_results(&mut self, results: RoaringBitmap) {
self.results = results;
}
pub fn into_bitmap(self) -> RoaringBitmap {
self.results
}
pub fn into_sorted(self) -> Vec<u32> {
let comparators = self.comparators;
let mut results = self.results.into_iter().collect::<Vec<u32>>();
if !results.is_empty() && !comparators.is_empty() {
results.sort_by(|a, b| {
for comparator in &comparators {
let (a, b, is_ascending) = match comparator {
SearchComparator::DocumentSet { set, ascending } => {
(set.contains(*a) as u32, set.contains(*b) as u32, *ascending)
}
SearchComparator::SortedSet { set, ascending } => {
let missing = if *ascending { u32::MAX } else { 0 };
(
*set.get(a).unwrap_or(&missing),
*set.get(b).unwrap_or(&missing),
*ascending,
)
}
SearchComparator::Field { .. } => continue,
};
let ordering = if is_ascending { a.cmp(&b) } else { b.cmp(&a) };
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
});
}
results
}
}