2021-04-01 14:42:23 +02:00
|
|
|
use std::{borrow::Cow, cmp::{self, Ordering}, collections::BinaryHeap};
|
2021-03-11 17:31:02 +01:00
|
|
|
use std::collections::{BTreeMap, HashMap, btree_map};
|
2021-04-13 15:06:12 +02:00
|
|
|
use std::collections::binary_heap::PeekMut;
|
2021-03-11 17:31:02 +01:00
|
|
|
use std::mem::take;
|
|
|
|
|
2021-03-11 11:48:55 +01:00
|
|
|
use roaring::RoaringBitmap;
|
|
|
|
|
2021-03-31 19:23:02 +02:00
|
|
|
use crate::{TreeLevel, search::build_dfa};
|
2021-03-11 11:48:55 +01:00
|
|
|
use crate::search::criteria::Query;
|
2021-03-11 17:31:02 +01:00
|
|
|
use crate::search::query_tree::{Operation, QueryKind};
|
2021-04-01 14:42:23 +02:00
|
|
|
use crate::search::{word_derivations, WordDerivationsCache};
|
2021-03-11 17:31:02 +01:00
|
|
|
use super::{Criterion, CriterionResult, Context, resolve_query_tree};
|
2021-03-11 11:48:55 +01:00
|
|
|
|
2021-04-13 18:25:38 +02:00
|
|
|
/// To be able to divide integers by the number of words in the query
|
|
|
|
/// we want to find a multiplier that allow us to divide by any number between 1 and 10.
|
|
|
|
/// We Choosed the LCM of all numbers between 1 and 10 as the multiplier (https://en.wikipedia.org/wiki/Least_common_multiple).
|
|
|
|
const LCM_10_FIRST_NUMBERS: u32 = 2520;
|
2021-04-15 10:44:27 +02:00
|
|
|
|
2021-03-11 11:48:55 +01:00
|
|
|
pub struct Attribute<'t> {
|
2021-03-31 19:23:02 +02:00
|
|
|
ctx: &'t dyn Context<'t>,
|
2021-03-11 11:48:55 +01:00
|
|
|
query_tree: Option<Operation>,
|
|
|
|
candidates: Option<RoaringBitmap>,
|
|
|
|
bucket_candidates: RoaringBitmap,
|
2021-03-23 15:25:46 +01:00
|
|
|
parent: Box<dyn Criterion + 't>,
|
2021-03-29 16:25:14 +02:00
|
|
|
flattened_query_tree: Option<Vec<Vec<Vec<Query>>>>,
|
2021-03-11 17:31:02 +01:00
|
|
|
current_buckets: Option<btree_map::IntoIter<u64, RoaringBitmap>>,
|
2021-03-11 11:48:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t> Attribute<'t> {
|
2021-03-31 19:23:02 +02:00
|
|
|
pub fn new(ctx: &'t dyn Context<'t>, parent: Box<dyn Criterion + 't>) -> Self {
|
2021-03-11 11:48:55 +01:00
|
|
|
Attribute {
|
|
|
|
ctx,
|
|
|
|
query_tree: None,
|
|
|
|
candidates: None,
|
|
|
|
bucket_candidates: RoaringBitmap::new(),
|
2021-03-23 15:25:46 +01:00
|
|
|
parent,
|
2021-03-11 17:31:02 +01:00
|
|
|
flattened_query_tree: None,
|
|
|
|
current_buckets: None,
|
2021-03-11 11:48:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t> Criterion for Attribute<'t> {
|
|
|
|
#[logging_timer::time("Attribute::{}")]
|
|
|
|
fn next(&mut self, wdcache: &mut WordDerivationsCache) -> anyhow::Result<Option<CriterionResult>> {
|
2021-03-11 17:31:02 +01:00
|
|
|
loop {
|
|
|
|
match (&self.query_tree, &mut self.candidates) {
|
|
|
|
(_, Some(candidates)) if candidates.is_empty() => {
|
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: self.query_tree.take(),
|
|
|
|
candidates: self.candidates.take(),
|
|
|
|
bucket_candidates: take(&mut self.bucket_candidates),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
(Some(qt), Some(candidates)) => {
|
2021-03-23 15:25:46 +01:00
|
|
|
let flattened_query_tree = self.flattened_query_tree.get_or_insert_with(|| {
|
|
|
|
flatten_query_tree(&qt)
|
|
|
|
});
|
2021-03-11 17:31:02 +01:00
|
|
|
|
2021-04-01 19:02:13 +02:00
|
|
|
let found_candidates = if candidates.len() < 1_000 {
|
2021-03-31 19:23:02 +02:00
|
|
|
let current_buckets = match self.current_buckets.as_mut() {
|
|
|
|
Some(current_buckets) => current_buckets,
|
|
|
|
None => {
|
|
|
|
let new_buckets = linear_compute_candidates(self.ctx, flattened_query_tree, candidates)?;
|
|
|
|
self.current_buckets.get_or_insert(new_buckets.into_iter())
|
|
|
|
},
|
|
|
|
};
|
2021-03-11 17:31:02 +01:00
|
|
|
|
2021-03-31 19:23:02 +02:00
|
|
|
match current_buckets.next() {
|
|
|
|
Some((_score, candidates)) => candidates,
|
|
|
|
None => {
|
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: self.query_tree.take(),
|
|
|
|
candidates: self.candidates.take(),
|
|
|
|
bucket_candidates: take(&mut self.bucket_candidates),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else {
|
2021-04-06 15:03:41 +02:00
|
|
|
let found_candidates = set_compute_candidates(self.ctx, flattened_query_tree, candidates, wdcache)?;
|
|
|
|
|
|
|
|
match found_candidates {
|
|
|
|
Some(candidates) => candidates,
|
|
|
|
None => {
|
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: self.query_tree.take(),
|
|
|
|
candidates: self.candidates.take(),
|
|
|
|
bucket_candidates: take(&mut self.bucket_candidates),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
}
|
2021-03-11 17:31:02 +01:00
|
|
|
};
|
|
|
|
|
2021-03-23 15:25:46 +01:00
|
|
|
candidates.difference_with(&found_candidates);
|
|
|
|
|
2021-03-11 17:31:02 +01:00
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: self.query_tree.clone(),
|
|
|
|
candidates: Some(found_candidates),
|
2021-03-23 15:25:46 +01:00
|
|
|
bucket_candidates: take(&mut self.bucket_candidates),
|
2021-03-11 17:31:02 +01:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
(Some(qt), None) => {
|
|
|
|
let query_tree_candidates = resolve_query_tree(self.ctx, &qt, &mut HashMap::new(), wdcache)?;
|
2021-04-15 12:22:44 +02:00
|
|
|
self.bucket_candidates |= &query_tree_candidates;
|
2021-03-11 17:31:02 +01:00
|
|
|
self.candidates = Some(query_tree_candidates);
|
|
|
|
},
|
|
|
|
(None, Some(_)) => {
|
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: self.query_tree.take(),
|
|
|
|
candidates: self.candidates.take(),
|
|
|
|
bucket_candidates: take(&mut self.bucket_candidates),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
(None, None) => {
|
2021-03-23 15:25:46 +01:00
|
|
|
match self.parent.next(wdcache)? {
|
|
|
|
Some(CriterionResult { query_tree: None, candidates: None, bucket_candidates }) => {
|
|
|
|
return Ok(Some(CriterionResult {
|
|
|
|
query_tree: None,
|
|
|
|
candidates: None,
|
|
|
|
bucket_candidates,
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
Some(CriterionResult { query_tree, candidates, bucket_candidates }) => {
|
|
|
|
self.query_tree = query_tree;
|
|
|
|
self.candidates = candidates;
|
2021-04-15 12:22:44 +02:00
|
|
|
self.bucket_candidates |= bucket_candidates;
|
2021-03-23 15:25:46 +01:00
|
|
|
self.flattened_query_tree = None;
|
|
|
|
self.current_buckets = None;
|
2021-03-11 17:31:02 +01:00
|
|
|
},
|
|
|
|
None => return Ok(None),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2021-03-11 11:48:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
/// WordLevelIterator is an pseudo-Iterator over intervals of word-position for one word,
|
|
|
|
/// it will begin at the first non-empty interval and will return every interval without
|
|
|
|
/// jumping over empty intervals.
|
2021-03-31 19:23:02 +02:00
|
|
|
struct WordLevelIterator<'t, 'q> {
|
|
|
|
inner: Box<dyn Iterator<Item =heed::Result<((&'t str, TreeLevel, u32, u32), RoaringBitmap)>> + 't>,
|
|
|
|
level: TreeLevel,
|
|
|
|
interval_size: u32,
|
2021-04-01 14:42:23 +02:00
|
|
|
word: Cow<'q, str>,
|
2021-03-31 19:23:02 +02:00
|
|
|
in_prefix_cache: bool,
|
|
|
|
inner_next: Option<(u32, u32, RoaringBitmap)>,
|
|
|
|
current_interval: Option<(u32, u32)>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> WordLevelIterator<'t, 'q> {
|
2021-04-01 14:42:23 +02:00
|
|
|
fn new(ctx: &'t dyn Context<'t>, word: Cow<'q, str>, in_prefix_cache: bool) -> heed::Result<Option<Self>> {
|
|
|
|
match ctx.word_position_last_level(&word, in_prefix_cache)? {
|
2021-03-31 19:23:02 +02:00
|
|
|
Some(level) => {
|
|
|
|
let interval_size = 4u32.pow(Into::<u8>::into(level.clone()) as u32);
|
2021-04-01 14:42:23 +02:00
|
|
|
let inner = ctx.word_position_iterator(&word, level, in_prefix_cache, None, None)?;
|
2021-03-31 19:23:02 +02:00
|
|
|
Ok(Some(Self { inner, level, interval_size, word, in_prefix_cache, inner_next: None, current_interval: None }))
|
|
|
|
},
|
|
|
|
None => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 12:22:44 +02:00
|
|
|
fn dig(&self, ctx: &'t dyn Context<'t>, level: &TreeLevel, left_interval: Option<u32>) -> heed::Result<Self> {
|
2021-03-31 19:23:02 +02:00
|
|
|
let level = level.min(&self.level).clone();
|
|
|
|
let interval_size = 4u32.pow(Into::<u8>::into(level.clone()) as u32);
|
2021-04-01 14:42:23 +02:00
|
|
|
let word = self.word.clone();
|
2021-03-31 19:23:02 +02:00
|
|
|
let in_prefix_cache = self.in_prefix_cache;
|
2021-04-15 12:22:44 +02:00
|
|
|
let inner = ctx.word_position_iterator(&word, level, in_prefix_cache, left_interval, None)?;
|
2021-03-31 19:23:02 +02:00
|
|
|
|
|
|
|
Ok(Self {inner, level, interval_size, word, in_prefix_cache, inner_next: None, current_interval: None})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn next(&mut self) -> heed::Result<Option<(u32, u32, RoaringBitmap)>> {
|
|
|
|
fn is_next_interval(last_right: u32, next_left: u32) -> bool { last_right + 1 == next_left }
|
|
|
|
|
|
|
|
let inner_next = match self.inner_next.take() {
|
|
|
|
Some(inner_next) => Some(inner_next),
|
|
|
|
None => self.inner.next().transpose()?.map(|((_, _, left, right), docids)| (left, right, docids)),
|
|
|
|
};
|
|
|
|
|
|
|
|
match inner_next {
|
|
|
|
Some((left, right, docids)) => {
|
|
|
|
match self.current_interval {
|
|
|
|
Some((last_left, last_right)) if !is_next_interval(last_right, left) => {
|
|
|
|
let blank_left = last_left + self.interval_size;
|
|
|
|
let blank_right = last_right + self.interval_size;
|
|
|
|
self.current_interval = Some((blank_left, blank_right));
|
|
|
|
self.inner_next = Some((left, right, docids));
|
|
|
|
Ok(Some((blank_left, blank_right, RoaringBitmap::new())))
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
self.current_interval = Some((left, right));
|
|
|
|
Ok(Some((left, right, docids)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
/// QueryLevelIterator is an pseudo-Iterator for a Query,
|
|
|
|
/// It contains WordLevelIterators and is chainned with other QueryLevelIterator.
|
2021-03-31 19:23:02 +02:00
|
|
|
struct QueryLevelIterator<'t, 'q> {
|
2021-04-15 10:44:27 +02:00
|
|
|
parent: Option<Box<QueryLevelIterator<'t, 'q>>>,
|
2021-03-31 19:23:02 +02:00
|
|
|
inner: Vec<WordLevelIterator<'t, 'q>>,
|
|
|
|
level: TreeLevel,
|
|
|
|
accumulator: Vec<Option<(u32, u32, RoaringBitmap)>>,
|
2021-04-15 10:44:27 +02:00
|
|
|
parent_accumulator: Vec<Option<(u32, u32, RoaringBitmap)>>,
|
2021-04-15 12:22:44 +02:00
|
|
|
interval_to_skip: usize,
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> QueryLevelIterator<'t, 'q> {
|
2021-04-01 14:42:23 +02:00
|
|
|
fn new(ctx: &'t dyn Context<'t>, queries: &'q Vec<Query>, wdcache: &mut WordDerivationsCache) -> anyhow::Result<Option<Self>> {
|
2021-03-31 19:23:02 +02:00
|
|
|
let mut inner = Vec::with_capacity(queries.len());
|
|
|
|
for query in queries {
|
2021-04-01 14:42:23 +02:00
|
|
|
match &query.kind {
|
|
|
|
QueryKind::Exact { word, .. } => {
|
|
|
|
if !query.prefix || ctx.in_prefix_cache(&word) {
|
|
|
|
let word = Cow::Borrowed(query.kind.word());
|
|
|
|
if let Some(word_level_iterator) = WordLevelIterator::new(ctx, word, query.prefix)? {
|
|
|
|
inner.push(word_level_iterator);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (word, _) in word_derivations(&word, true, 0, ctx.words_fst(), wdcache)? {
|
|
|
|
let word = Cow::Owned(word.to_owned());
|
|
|
|
if let Some(word_level_iterator) = WordLevelIterator::new(ctx, word, false)? {
|
|
|
|
inner.push(word_level_iterator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
QueryKind::Tolerant { typo, word } => {
|
|
|
|
for (word, _) in word_derivations(&word, query.prefix, *typo, ctx.words_fst(), wdcache)? {
|
|
|
|
let word = Cow::Owned(word.to_owned());
|
|
|
|
if let Some(word_level_iterator) = WordLevelIterator::new(ctx, word, false)? {
|
|
|
|
inner.push(word_level_iterator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let highest = inner.iter().max_by_key(|wli| wli.level).map(|wli| wli.level.clone());
|
|
|
|
match highest {
|
|
|
|
Some(level) => Ok(Some(Self {
|
2021-04-15 10:44:27 +02:00
|
|
|
parent: None,
|
2021-03-31 19:23:02 +02:00
|
|
|
inner,
|
|
|
|
level,
|
|
|
|
accumulator: vec![],
|
2021-04-15 10:44:27 +02:00
|
|
|
parent_accumulator: vec![],
|
2021-04-15 12:22:44 +02:00
|
|
|
interval_to_skip: 0,
|
2021-03-31 19:23:02 +02:00
|
|
|
})),
|
|
|
|
None => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
fn parent(&mut self, parent: QueryLevelIterator<'t, 'q>) -> &Self {
|
|
|
|
self.parent = Some(Box::new(parent));
|
2021-03-31 19:23:02 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
/// create a new QueryLevelIterator with a lower level than the current one.
|
2021-03-31 19:23:02 +02:00
|
|
|
fn dig(&self, ctx: &'t dyn Context<'t>) -> heed::Result<Self> {
|
2021-04-15 10:44:27 +02:00
|
|
|
let (level, parent) = match &self.parent {
|
|
|
|
Some(parent) => {
|
|
|
|
let parent = parent.dig(ctx)?;
|
|
|
|
(parent.level.min(self.level), Some(Box::new(parent)))
|
2021-03-31 19:23:02 +02:00
|
|
|
},
|
|
|
|
None => (self.level.saturating_sub(1), None),
|
|
|
|
};
|
|
|
|
|
2021-04-15 12:22:44 +02:00
|
|
|
let left_interval = self.accumulator.get(self.interval_to_skip).map(|opt| opt.as_ref().map(|(left, _, _)| *left)).flatten();
|
2021-03-31 19:23:02 +02:00
|
|
|
let mut inner = Vec::with_capacity(self.inner.len());
|
|
|
|
for word_level_iterator in self.inner.iter() {
|
2021-04-15 12:22:44 +02:00
|
|
|
inner.push(word_level_iterator.dig(ctx, &level, left_interval)?);
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
|
2021-04-15 12:22:44 +02:00
|
|
|
Ok(Self {parent, inner, level, accumulator: vec![], parent_accumulator: vec![], interval_to_skip: 0})
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn inner_next(&mut self, level: TreeLevel) -> heed::Result<Option<(u32, u32, RoaringBitmap)>> {
|
|
|
|
let mut accumulated: Option<(u32, u32, RoaringBitmap)> = None;
|
|
|
|
let u8_level = Into::<u8>::into(level);
|
|
|
|
let interval_size = 4u32.pow(u8_level as u32);
|
|
|
|
for wli in self.inner.iter_mut() {
|
|
|
|
let wli_u8_level = Into::<u8>::into(wli.level.clone());
|
|
|
|
let accumulated_count = 4u32.pow((u8_level - wli_u8_level) as u32);
|
|
|
|
for _ in 0..accumulated_count {
|
|
|
|
if let Some((next_left, _, next_docids)) = wli.next()? {
|
2021-04-15 12:22:44 +02:00
|
|
|
accumulated = match accumulated.take(){
|
|
|
|
Some((acc_left, acc_right, mut acc_docids)) => {
|
|
|
|
acc_docids |= next_docids;
|
|
|
|
Some((acc_left, acc_right, acc_docids))
|
|
|
|
},
|
|
|
|
None => Some((next_left, next_left + interval_size, next_docids)),
|
|
|
|
};
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(accumulated)
|
|
|
|
}
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
/// return the next meta-interval created from inner WordLevelIterators,
|
|
|
|
/// and from eventual chainned QueryLevelIterator.
|
2021-04-15 12:22:44 +02:00
|
|
|
fn next(&mut self, allowed_candidates: &RoaringBitmap, tree_level: TreeLevel) -> heed::Result<Option<(u32, u32, RoaringBitmap)>> {
|
2021-04-15 10:44:27 +02:00
|
|
|
let parent_result = match self.parent.as_mut() {
|
|
|
|
Some(parent) => {
|
2021-04-15 12:22:44 +02:00
|
|
|
Some(parent.next(allowed_candidates, tree_level)?)
|
2021-03-31 19:23:02 +02:00
|
|
|
},
|
|
|
|
None => None,
|
|
|
|
};
|
|
|
|
|
2021-04-15 10:44:27 +02:00
|
|
|
match parent_result {
|
2021-04-15 12:22:44 +02:00
|
|
|
Some(parent_next) => {
|
|
|
|
let inner_next = self.inner_next(tree_level)?;
|
|
|
|
self.interval_to_skip += self.accumulator.iter().zip(self.parent_accumulator.iter()).skip(self.interval_to_skip).take_while(|current| {
|
|
|
|
match current {
|
|
|
|
(Some((_, _, inner)), Some((_, _, parent))) => {
|
|
|
|
inner.is_disjoint(allowed_candidates) && parent.is_empty()
|
|
|
|
},
|
|
|
|
(Some((_, _, inner)), None) => {
|
|
|
|
inner.is_disjoint(allowed_candidates)
|
|
|
|
},
|
|
|
|
(None, Some((_, _, parent))) => {
|
|
|
|
parent.is_empty()
|
|
|
|
},
|
|
|
|
(None, None) => true,
|
|
|
|
}
|
|
|
|
}).count();
|
2021-03-31 19:23:02 +02:00
|
|
|
self.accumulator.push(inner_next);
|
2021-04-15 10:44:27 +02:00
|
|
|
self.parent_accumulator.push(parent_next);
|
2021-04-15 12:22:44 +02:00
|
|
|
let mut merged_interval: Option<(u32, u32, RoaringBitmap)> = None;
|
|
|
|
|
|
|
|
for current in self.accumulator.iter().rev().zip(self.parent_accumulator.iter()).skip(self.interval_to_skip) {
|
2021-03-31 19:23:02 +02:00
|
|
|
if let (Some((left_a, right_a, a)), Some((left_b, right_b, b))) = current {
|
2021-04-15 12:22:44 +02:00
|
|
|
match merged_interval.as_mut() {
|
|
|
|
Some((_, _, merged_docids)) => *merged_docids |= a & b,
|
|
|
|
None => merged_interval = Some((left_a + left_b, right_a + right_b, a & b)),
|
|
|
|
}
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
2021-04-15 12:22:44 +02:00
|
|
|
Ok(merged_interval)
|
2021-03-31 19:23:02 +02:00
|
|
|
},
|
|
|
|
None => {
|
2021-04-15 12:22:44 +02:00
|
|
|
let level = self.level;
|
|
|
|
match self.inner_next(level)? {
|
|
|
|
Some((left, right, mut candidates)) => {
|
|
|
|
self.accumulator = vec![Some((left, right, RoaringBitmap::new()))];
|
|
|
|
candidates &= allowed_candidates;
|
|
|
|
Ok(Some((left, right, candidates)))
|
|
|
|
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
self.accumulator = vec![None];
|
|
|
|
Ok(None)
|
|
|
|
},
|
|
|
|
}
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Branch<'t, 'q> {
|
|
|
|
query_level_iterator: QueryLevelIterator<'t, 'q>,
|
2021-04-12 11:19:25 +02:00
|
|
|
last_result: (u32, u32, RoaringBitmap),
|
2021-03-31 19:23:02 +02:00
|
|
|
tree_level: TreeLevel,
|
|
|
|
branch_size: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> Branch<'t, 'q> {
|
2021-04-15 12:22:44 +02:00
|
|
|
fn next(&mut self, allowed_candidates: &RoaringBitmap) -> heed::Result<bool> {
|
|
|
|
let tree_level = self.query_level_iterator.level;
|
|
|
|
match self.query_level_iterator.next(allowed_candidates, tree_level)? {
|
|
|
|
Some(last_result) => {
|
2021-04-13 15:06:12 +02:00
|
|
|
self.last_result = last_result;
|
|
|
|
self.tree_level = tree_level;
|
|
|
|
Ok(true)
|
|
|
|
},
|
2021-04-15 12:22:44 +02:00
|
|
|
None => Ok(false),
|
2021-04-13 15:06:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 12:22:44 +02:00
|
|
|
fn dig(&mut self, ctx: &'t dyn Context<'t>) -> heed::Result<()> {
|
|
|
|
self.query_level_iterator = self.query_level_iterator.dig(ctx)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lazy_next(&mut self) {
|
|
|
|
let u8_level = Into::<u8>::into(self.tree_level.clone());
|
|
|
|
let interval_size = 4u32.pow(u8_level as u32);
|
|
|
|
let (left, right, _) = self.last_result;
|
|
|
|
|
|
|
|
self.last_result = (left + interval_size, right + interval_size, RoaringBitmap::new());
|
|
|
|
}
|
|
|
|
|
2021-04-13 15:06:12 +02:00
|
|
|
fn compute_rank(&self) -> u32 {
|
2021-04-12 11:19:25 +02:00
|
|
|
// we compute a rank from the left interval.
|
2021-04-13 15:06:12 +02:00
|
|
|
let (left, _, _) = self.last_result;
|
2021-04-13 18:25:38 +02:00
|
|
|
left.saturating_sub((0..self.branch_size).sum()) * LCM_10_FIRST_NUMBERS / self.branch_size
|
2021-04-13 15:06:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
let self_rank = self.compute_rank();
|
|
|
|
let other_rank = other.compute_rank();
|
2021-04-12 11:19:25 +02:00
|
|
|
let left_cmp = self_rank.cmp(&other_rank).reverse();
|
2021-04-15 12:22:44 +02:00
|
|
|
// on level: lower is better,
|
|
|
|
// we want to dig faster into levels on interesting branches.
|
|
|
|
let level_cmp = self.tree_level.cmp(&other.tree_level).reverse();
|
2021-04-12 11:19:25 +02:00
|
|
|
|
2021-04-15 12:22:44 +02:00
|
|
|
left_cmp.then(level_cmp).then(self.last_result.2.len().cmp(&other.last_result.2.len()))
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> Ord for Branch<'t, 'q> {
|
|
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
|
|
self.cmp(other)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> PartialOrd for Branch<'t, 'q> {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
|
|
Some(self.cmp(other))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> PartialEq for Branch<'t, 'q> {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.cmp(other) == Ordering::Equal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'t, 'q> Eq for Branch<'t, 'q> {}
|
|
|
|
|
|
|
|
fn initialize_query_level_iterators<'t, 'q>(
|
|
|
|
ctx: &'t dyn Context<'t>,
|
|
|
|
branches: &'q Vec<Vec<Vec<Query>>>,
|
2021-04-15 12:22:44 +02:00
|
|
|
allowed_candidates: &RoaringBitmap,
|
2021-04-01 14:42:23 +02:00
|
|
|
wdcache: &mut WordDerivationsCache,
|
|
|
|
) -> anyhow::Result<BinaryHeap<Branch<'t, 'q>>> {
|
2021-03-31 19:23:02 +02:00
|
|
|
|
|
|
|
let mut positions = BinaryHeap::with_capacity(branches.len());
|
|
|
|
for branch in branches {
|
|
|
|
let mut branch_positions = Vec::with_capacity(branch.len());
|
2021-04-01 19:02:13 +02:00
|
|
|
for queries in branch {
|
|
|
|
match QueryLevelIterator::new(ctx, queries, wdcache)? {
|
2021-03-31 19:23:02 +02:00
|
|
|
Some(qli) => branch_positions.push(qli),
|
|
|
|
None => {
|
|
|
|
// the branch seems to be invalid, so we skip it.
|
|
|
|
branch_positions.clear();
|
|
|
|
break;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// QueryLevelIterator need to be sorted by level and folded in descending order.
|
|
|
|
branch_positions.sort_unstable_by_key(|qli| qli.level);
|
|
|
|
let folded_query_level_iterators = branch_positions
|
|
|
|
.into_iter()
|
2021-04-01 19:02:13 +02:00
|
|
|
.fold(None, |fold: Option<QueryLevelIterator>, mut qli| match fold {
|
|
|
|
Some(fold) => {
|
2021-04-15 10:44:27 +02:00
|
|
|
qli.parent(fold);
|
2021-04-01 19:02:13 +02:00
|
|
|
Some(qli)
|
2021-03-31 19:23:02 +02:00
|
|
|
},
|
|
|
|
None => Some(qli),
|
|
|
|
});
|
|
|
|
|
|
|
|
if let Some(mut folded_query_level_iterators) = folded_query_level_iterators {
|
2021-04-15 12:22:44 +02:00
|
|
|
let tree_level = folded_query_level_iterators.level;
|
|
|
|
let last_result = folded_query_level_iterators.next(allowed_candidates, tree_level)?;
|
2021-04-12 11:19:25 +02:00
|
|
|
if let Some(last_result) = last_result {
|
|
|
|
let branch = Branch {
|
|
|
|
last_result,
|
|
|
|
tree_level,
|
|
|
|
query_level_iterator: folded_query_level_iterators,
|
|
|
|
branch_size: branch.len() as u32,
|
|
|
|
};
|
|
|
|
positions.push(branch);
|
|
|
|
}
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(positions)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_compute_candidates<'t>(
|
|
|
|
ctx: &'t dyn Context<'t>,
|
|
|
|
branches: &Vec<Vec<Vec<Query>>>,
|
|
|
|
allowed_candidates: &RoaringBitmap,
|
2021-04-01 14:42:23 +02:00
|
|
|
wdcache: &mut WordDerivationsCache,
|
2021-04-06 15:03:41 +02:00
|
|
|
) -> anyhow::Result<Option<RoaringBitmap>>
|
2021-03-31 19:23:02 +02:00
|
|
|
{
|
2021-04-15 12:22:44 +02:00
|
|
|
let mut branches_heap = initialize_query_level_iterators(ctx, branches, allowed_candidates, wdcache)?;
|
2021-03-31 19:23:02 +02:00
|
|
|
let lowest_level = TreeLevel::min_value();
|
2021-04-13 15:06:12 +02:00
|
|
|
let mut final_candidates: Option<(u32, RoaringBitmap)> = None;
|
2021-04-15 12:22:44 +02:00
|
|
|
let mut allowed_candidates = allowed_candidates.clone();
|
2021-03-31 19:23:02 +02:00
|
|
|
|
|
|
|
while let Some(mut branch) = branches_heap.peek_mut() {
|
|
|
|
let is_lowest_level = branch.tree_level == lowest_level;
|
2021-04-13 15:06:12 +02:00
|
|
|
let branch_rank = branch.compute_rank();
|
2021-04-15 12:22:44 +02:00
|
|
|
// if current is worst than best we break to return
|
|
|
|
// candidates that correspond to the best rank
|
|
|
|
if let Some((best_rank, _)) = final_candidates { if branch_rank > best_rank { break; } }
|
|
|
|
let _left = branch.last_result.0;
|
|
|
|
let candidates = take(&mut branch.last_result.2);
|
2021-04-12 11:19:25 +02:00
|
|
|
if candidates.is_empty() {
|
|
|
|
// we don't have candidates, get next interval.
|
2021-04-15 12:22:44 +02:00
|
|
|
if !branch.next(&allowed_candidates)? { PeekMut::pop(branch); }
|
2021-04-12 11:19:25 +02:00
|
|
|
}
|
|
|
|
else if is_lowest_level {
|
2021-04-15 12:22:44 +02:00
|
|
|
// we have candidates, but we can't dig deeper.
|
|
|
|
allowed_candidates -= &candidates;
|
2021-04-13 15:06:12 +02:00
|
|
|
final_candidates = match final_candidates.take() {
|
2021-04-15 12:22:44 +02:00
|
|
|
// we add current candidates to best candidates
|
2021-04-13 15:06:12 +02:00
|
|
|
Some((best_rank, mut best_candidates)) => {
|
2021-04-15 12:22:44 +02:00
|
|
|
best_candidates |= candidates;
|
|
|
|
branch.lazy_next();
|
|
|
|
Some((best_rank, best_candidates))
|
2021-04-13 15:06:12 +02:00
|
|
|
},
|
|
|
|
// we take current candidates as best candidates
|
|
|
|
None => {
|
2021-04-15 12:22:44 +02:00
|
|
|
branch.lazy_next();
|
2021-04-13 15:06:12 +02:00
|
|
|
Some((branch_rank, candidates))
|
|
|
|
},
|
|
|
|
};
|
2021-04-12 11:19:25 +02:00
|
|
|
} else {
|
|
|
|
// we have candidates, lets dig deeper in levels.
|
2021-04-15 12:22:44 +02:00
|
|
|
branch.dig(ctx)?;
|
|
|
|
if !branch.next(&allowed_candidates)? { PeekMut::pop(branch); }
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
2021-04-01 19:02:13 +02:00
|
|
|
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
|
2021-04-13 15:06:12 +02:00
|
|
|
Ok(final_candidates.map(|(_rank, candidates)| {
|
|
|
|
candidates
|
|
|
|
}))
|
2021-03-31 19:23:02 +02:00
|
|
|
}
|
|
|
|
|
2021-03-11 17:31:02 +01:00
|
|
|
fn linear_compute_candidates(
|
|
|
|
ctx: &dyn Context,
|
2021-03-29 16:25:14 +02:00
|
|
|
branches: &Vec<Vec<Vec<Query>>>,
|
2021-03-11 17:31:02 +01:00
|
|
|
allowed_candidates: &RoaringBitmap,
|
|
|
|
) -> anyhow::Result<BTreeMap<u64, RoaringBitmap>>
|
|
|
|
{
|
2021-03-29 16:25:14 +02:00
|
|
|
fn compute_candidate_rank(branches: &Vec<Vec<Vec<Query>>>, words_positions: HashMap<String, RoaringBitmap>) -> u64 {
|
2021-03-11 17:31:02 +01:00
|
|
|
let mut min_rank = u64::max_value();
|
|
|
|
for branch in branches {
|
2021-03-29 16:25:14 +02:00
|
|
|
|
2021-03-24 18:20:13 +01:00
|
|
|
let branch_len = branch.len();
|
|
|
|
let mut branch_rank = Vec::with_capacity(branch_len);
|
2021-03-29 16:25:14 +02:00
|
|
|
for derivates in branch {
|
|
|
|
let mut position = None;
|
|
|
|
for Query { prefix, kind } in derivates {
|
|
|
|
// find the best position of the current word in the document.
|
|
|
|
let current_position = match kind {
|
|
|
|
QueryKind::Exact { word, .. } => {
|
|
|
|
if *prefix {
|
|
|
|
word_derivations(word, true, 0, &words_positions)
|
|
|
|
.flat_map(|positions| positions.iter().next()).min()
|
|
|
|
} else {
|
|
|
|
words_positions.get(word)
|
|
|
|
.map(|positions| positions.iter().next())
|
|
|
|
.flatten()
|
|
|
|
}
|
|
|
|
},
|
|
|
|
QueryKind::Tolerant { typo, word } => {
|
|
|
|
word_derivations(word, *prefix, *typo, &words_positions)
|
|
|
|
.flat_map(|positions| positions.iter().next()).min()
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
match (position, current_position) {
|
|
|
|
(Some(p), Some(cp)) => position = Some(cmp::min(p, cp)),
|
|
|
|
(None, Some(cp)) => position = Some(cp),
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
2021-03-11 17:31:02 +01:00
|
|
|
|
|
|
|
// if a position is found, we add it to the branch score,
|
|
|
|
// otherwise the branch is considered as unfindable in this document and we break.
|
|
|
|
if let Some(position) = position {
|
2021-03-24 18:20:13 +01:00
|
|
|
branch_rank.push(position as u64);
|
2021-03-11 17:31:02 +01:00
|
|
|
} else {
|
2021-03-24 18:20:13 +01:00
|
|
|
branch_rank.clear();
|
2021-03-11 17:31:02 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 18:20:13 +01:00
|
|
|
|
|
|
|
if !branch_rank.is_empty() {
|
|
|
|
branch_rank.sort_unstable();
|
|
|
|
// because several words in same query can't match all a the position 0,
|
|
|
|
// we substract the word index to the position.
|
|
|
|
let branch_rank: u64 = branch_rank.into_iter().enumerate().map(|(i, r)| r - i as u64).sum();
|
|
|
|
// here we do the means of the words of the branch
|
2021-04-13 18:25:38 +02:00
|
|
|
min_rank = min_rank.min(branch_rank * LCM_10_FIRST_NUMBERS as u64 / branch_len as u64);
|
2021-03-24 18:20:13 +01:00
|
|
|
}
|
2021-03-11 17:31:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
min_rank
|
|
|
|
}
|
|
|
|
|
|
|
|
fn word_derivations<'a>(
|
|
|
|
word: &str,
|
|
|
|
is_prefix: bool,
|
|
|
|
max_typo: u8,
|
|
|
|
words_positions: &'a HashMap<String, RoaringBitmap>,
|
|
|
|
) -> impl Iterator<Item = &'a RoaringBitmap>
|
|
|
|
{
|
|
|
|
let dfa = build_dfa(word, max_typo, is_prefix);
|
|
|
|
words_positions.iter().filter_map(move |(document_word, positions)| {
|
|
|
|
use levenshtein_automata::Distance;
|
|
|
|
match dfa.eval(document_word) {
|
|
|
|
Distance::Exact(_) => Some(positions),
|
|
|
|
Distance::AtLeast(_) => None,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut candidates = BTreeMap::new();
|
|
|
|
for docid in allowed_candidates {
|
|
|
|
let words_positions = ctx.docid_words_positions(docid)?;
|
|
|
|
let rank = compute_candidate_rank(branches, words_positions);
|
|
|
|
candidates.entry(rank).or_insert_with(RoaringBitmap::new).insert(docid);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(candidates)
|
|
|
|
}
|
|
|
|
|
2021-03-11 11:48:55 +01:00
|
|
|
// TODO can we keep refs of Query
|
2021-03-29 16:25:14 +02:00
|
|
|
fn flatten_query_tree(query_tree: &Operation) -> Vec<Vec<Vec<Query>>> {
|
2021-03-11 11:48:55 +01:00
|
|
|
use crate::search::criteria::Operation::{And, Or, Consecutive};
|
|
|
|
|
2021-03-29 16:25:14 +02:00
|
|
|
fn and_recurse(head: &Operation, tail: &[Operation]) -> Vec<Vec<Vec<Query>>> {
|
2021-03-11 11:48:55 +01:00
|
|
|
match tail.split_first() {
|
|
|
|
Some((thead, tail)) => {
|
|
|
|
let tail = and_recurse(thead, tail);
|
|
|
|
let mut out = Vec::new();
|
|
|
|
for array in recurse(head) {
|
|
|
|
for tail_array in &tail {
|
|
|
|
let mut array = array.clone();
|
|
|
|
array.extend(tail_array.iter().cloned());
|
|
|
|
out.push(array);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out
|
|
|
|
},
|
|
|
|
None => recurse(head),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 16:25:14 +02:00
|
|
|
fn recurse(op: &Operation) -> Vec<Vec<Vec<Query>>> {
|
2021-03-11 11:48:55 +01:00
|
|
|
match op {
|
|
|
|
And(ops) | Consecutive(ops) => {
|
|
|
|
ops.split_first().map_or_else(Vec::new, |(h, t)| and_recurse(h, t))
|
|
|
|
},
|
2021-03-29 16:25:14 +02:00
|
|
|
Or(_, ops) => if ops.iter().all(|op| op.query().is_some()) {
|
|
|
|
vec![vec![ops.iter().flat_map(|op| op.query()).cloned().collect()]]
|
|
|
|
} else {
|
|
|
|
ops.into_iter().map(recurse).flatten().collect()
|
|
|
|
},
|
|
|
|
Operation::Query(query) => vec![vec![vec![query.clone()]]],
|
2021-03-11 11:48:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
recurse(query_tree)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use big_s::S;
|
|
|
|
|
|
|
|
use crate::search::criteria::QueryKind;
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
2021-03-11 17:31:02 +01:00
|
|
|
fn simple_flatten_query_tree() {
|
2021-03-11 11:48:55 +01:00
|
|
|
let query_tree = Operation::Or(false, vec![
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("manythefish")) }),
|
|
|
|
Operation::And(vec![
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("manythe")) }),
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("fish")) }),
|
|
|
|
]),
|
|
|
|
Operation::And(vec![
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("many")) }),
|
|
|
|
Operation::Or(false, vec![
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("thefish")) }),
|
|
|
|
Operation::And(vec![
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("the")) }),
|
|
|
|
Operation::Query(Query { prefix: false, kind: QueryKind::exact(S("fish")) }),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
]),
|
|
|
|
]);
|
|
|
|
|
|
|
|
let expected = vec![
|
2021-03-29 16:25:14 +02:00
|
|
|
vec![vec![Query { prefix: false, kind: QueryKind::exact(S("manythefish")) }]],
|
2021-03-11 11:48:55 +01:00
|
|
|
vec![
|
2021-03-29 16:25:14 +02:00
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("manythe")) }],
|
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("fish")) }],
|
2021-03-11 11:48:55 +01:00
|
|
|
],
|
|
|
|
vec![
|
2021-03-29 16:25:14 +02:00
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("many")) }],
|
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("thefish")) }],
|
2021-03-11 11:48:55 +01:00
|
|
|
],
|
|
|
|
vec![
|
2021-03-29 16:25:14 +02:00
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("many")) }],
|
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("the")) }],
|
|
|
|
vec![Query { prefix: false, kind: QueryKind::exact(S("fish")) }],
|
2021-03-11 11:48:55 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
|
2021-03-11 17:31:02 +01:00
|
|
|
let result = flatten_query_tree(&query_tree);
|
2021-03-11 11:48:55 +01:00
|
|
|
assert_eq!(expected, result);
|
|
|
|
}
|
|
|
|
}
|