/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::search::*; use roaring::RoaringBitmap; struct State { pub op: SearchFilter, pub bm: Option, } 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) -> Self { if self.filters.is_empty() { self.filters = filters; } else { self.filters.extend(filters); } self } pub fn with_comparators(mut self, comparators: Vec) -> 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) -> Self { Self { results, comparators, } } pub fn with_comparators(mut self, comparators: Vec) -> 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 { let comparators = self.comparators; let mut results = self.results.into_iter().collect::>(); 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 } }