meilisearch/milli/src/search/new/mod.rs

290 lines
9.4 KiB
Rust
Raw Normal View History

mod db_cache;
2023-03-09 22:20:29 +08:00
mod distinct;
mod graph_based_ranking_rule;
mod interner;
mod logger;
mod query_graph;
mod query_term;
mod ranking_rule_graph;
mod ranking_rules;
mod resolve_query_graph;
2023-03-09 18:12:31 +08:00
// TODO: documentation + comments
mod small_bitmap;
2023-03-09 18:12:31 +08:00
// TODO: documentation + comments
mod sort;
2023-03-09 18:12:31 +08:00
// TODO: documentation + comments
mod words;
2023-03-18 22:04:34 +08:00
// #[cfg(test)]
use std::collections::{BTreeSet, HashSet};
2023-03-08 16:55:53 +08:00
use charabia::Tokenize;
use db_cache::DatabaseCache;
2023-03-18 22:04:34 +08:00
use graph_based_ranking_rule::{Proximity, Typo};
use heed::RoTxn;
2023-03-18 22:04:34 +08:00
use interner::DedupInterner;
2023-03-19 22:15:58 +08:00
pub use logger::detailed::DetailedSearchLogger;
pub use logger::{DefaultSearchLogger, SearchLogger};
2023-03-18 22:04:34 +08:00
use query_graph::{QueryGraph, QueryNode, QueryNodeData};
use query_term::{located_query_terms_from_string, Phrase, QueryTerm};
use ranking_rules::{bucket_sort, PlaceholderQuery, RankingRuleOutput, RankingRuleQueryTrait};
use resolve_query_graph::{resolve_query_graph, QueryTermDocIdsCache};
use roaring::RoaringBitmap;
2023-03-18 22:04:34 +08:00
use words::Words;
2023-03-08 16:55:53 +08:00
2023-03-18 22:04:34 +08:00
use self::ranking_rules::RankingRule;
2023-03-19 22:15:58 +08:00
use crate::{Filter, Index, MatchingWords, Result, Search, SearchResult, TermsMatchingStrategy};
/// A structure used throughout the execution of a search query.
2023-03-13 21:03:48 +08:00
pub struct SearchContext<'ctx> {
pub index: &'ctx Index,
pub txn: &'ctx RoTxn<'ctx>,
pub db_cache: DatabaseCache<'ctx>,
2023-03-14 23:37:47 +08:00
pub word_interner: DedupInterner<String>,
pub phrase_interner: DedupInterner<Phrase>,
pub term_interner: DedupInterner<QueryTerm>,
pub term_docids: QueryTermDocIdsCache,
}
2023-03-13 21:03:48 +08:00
impl<'ctx> SearchContext<'ctx> {
pub fn new(index: &'ctx Index, txn: &'ctx RoTxn<'ctx>) -> Self {
Self {
index,
txn,
db_cache: <_>::default(),
word_interner: <_>::default(),
phrase_interner: <_>::default(),
term_interner: <_>::default(),
term_docids: <_>::default(),
}
}
}
/// Apply the [`TermsMatchingStrategy`] to the query graph and resolve it.
#[allow(clippy::too_many_arguments)]
2023-03-13 21:03:48 +08:00
fn resolve_maximally_reduced_query_graph<'ctx>(
ctx: &mut SearchContext<'ctx>,
universe: &RoaringBitmap,
query_graph: &QueryGraph,
matching_strategy: TermsMatchingStrategy,
logger: &mut dyn SearchLogger<QueryGraph>,
) -> Result<RoaringBitmap> {
let mut graph = query_graph.clone();
let mut positions_to_remove = match matching_strategy {
TermsMatchingStrategy::Last => {
let mut all_positions = BTreeSet::new();
2023-03-14 23:37:47 +08:00
for (_, n) in query_graph.nodes.iter() {
match &n.data {
QueryNodeData::Term(term) => {
all_positions.extend(term.positions.clone().into_iter());
}
2023-03-14 23:37:47 +08:00
QueryNodeData::Deleted | QueryNodeData::Start | QueryNodeData::End => {}
}
}
all_positions.into_iter().collect()
}
TermsMatchingStrategy::All => vec![],
};
// don't remove the first term
if !positions_to_remove.is_empty() {
positions_to_remove.remove(0);
}
loop {
if positions_to_remove.is_empty() {
break;
} else {
let position_to_remove = positions_to_remove.pop().unwrap();
2023-03-08 20:26:29 +08:00
let _ = graph.remove_words_starting_at_position(position_to_remove);
}
}
logger.query_for_universe(&graph);
let docids = resolve_query_graph(ctx, &graph, universe)?;
Ok(docids)
}
/// Return the list of initialised ranking rules to be used for a placeholder search.
2023-03-13 21:03:48 +08:00
fn get_ranking_rules_for_placeholder_search<'ctx>(
ctx: &SearchContext<'ctx>,
) -> Result<Vec<Box<dyn RankingRule<'ctx, PlaceholderQuery>>>> {
// let sort = false;
// let mut asc = HashSet::new();
// let mut desc = HashSet::new();
let /*mut*/ ranking_rules: Vec<Box<dyn RankingRule<PlaceholderQuery>>> = vec![];
let settings_ranking_rules = ctx.index.criteria(ctx.txn)?;
for rr in settings_ranking_rules {
// Add Words before any of: typo, proximity, attribute, exactness
match rr {
crate::Criterion::Words
| crate::Criterion::Typo
| crate::Criterion::Attribute
| crate::Criterion::Proximity
| crate::Criterion::Exactness => continue,
crate::Criterion::Sort => todo!(),
crate::Criterion::Asc(_) => todo!(),
crate::Criterion::Desc(_) => todo!(),
}
}
Ok(ranking_rules)
}
/// Return the list of initialised ranking rules to be used for a query graph search.
2023-03-13 21:03:48 +08:00
fn get_ranking_rules_for_query_graph_search<'ctx>(
ctx: &SearchContext<'ctx>,
terms_matching_strategy: TermsMatchingStrategy,
2023-03-13 21:03:48 +08:00
) -> Result<Vec<Box<dyn RankingRule<'ctx, QueryGraph>>>> {
// query graph search
let mut words = false;
let mut typo = false;
let mut proximity = false;
let sort = false;
let attribute = false;
let exactness = false;
let mut asc = HashSet::new();
let mut desc = HashSet::new();
let mut ranking_rules: Vec<Box<dyn RankingRule<QueryGraph>>> = vec![];
let settings_ranking_rules = ctx.index.criteria(ctx.txn)?;
for rr in settings_ranking_rules {
// Add Words before any of: typo, proximity, attribute, exactness
match rr {
crate::Criterion::Typo
| crate::Criterion::Attribute
| crate::Criterion::Proximity
| crate::Criterion::Exactness => {
if !words {
ranking_rules.push(Box::new(Words::new(terms_matching_strategy)));
words = true;
}
}
_ => {}
}
match rr {
crate::Criterion::Words => {
if words {
continue;
}
ranking_rules.push(Box::new(Words::new(terms_matching_strategy)));
words = true;
}
crate::Criterion::Typo => {
if typo {
continue;
}
typo = true;
ranking_rules.push(Box::<Typo>::default());
}
crate::Criterion::Proximity => {
if proximity {
continue;
}
proximity = true;
ranking_rules.push(Box::<Proximity>::default());
}
crate::Criterion::Attribute => {
if attribute {
continue;
}
todo!();
// attribute = false;
}
crate::Criterion::Sort => {
if sort {
continue;
}
todo!();
// sort = false;
}
crate::Criterion::Exactness => {
if exactness {
continue;
}
todo!();
// exactness = false;
}
crate::Criterion::Asc(field) => {
if asc.contains(&field) {
continue;
}
asc.insert(field);
2023-03-14 23:37:47 +08:00
// TODO
}
crate::Criterion::Desc(field) => {
if desc.contains(&field) {
continue;
}
desc.insert(field);
todo!();
}
}
}
Ok(ranking_rules)
}
#[allow(clippy::too_many_arguments)]
2023-03-13 21:03:48 +08:00
pub fn execute_search<'ctx>(
ctx: &mut SearchContext<'ctx>,
query: &str,
terms_matching_strategy: TermsMatchingStrategy,
filters: Option<Filter>,
from: usize,
length: usize,
placeholder_search_logger: &mut dyn SearchLogger<PlaceholderQuery>,
query_graph_logger: &mut dyn SearchLogger<QueryGraph>,
2023-03-18 22:04:34 +08:00
) -> Result<SearchResult> {
assert!(!query.is_empty());
let query_terms = located_query_terms_from_string(ctx, query.tokenize(), None)?;
let graph = QueryGraph::from_query(ctx, query_terms)?;
2023-03-18 22:04:34 +08:00
let mut universe = if let Some(filters) = filters {
filters.evaluate(ctx.txn, ctx.index)?
} else {
ctx.index.documents_ids(ctx.txn)?
};
// TODO: other way to tell whether it is a placeholder search
// This way of doing things is not correct because if someone searches
// for a word that does not appear in any document, the word will be removed
// from the graph and thus its number of nodes will be == 2
// But in that case, we should return no results.
//
// The search is a placeholder search only if there are no tokens?
2023-03-18 22:04:34 +08:00
let documents_ids = if graph.nodes.len() > 2 {
universe = resolve_maximally_reduced_query_graph(
ctx,
&universe,
&graph,
terms_matching_strategy,
query_graph_logger,
)?;
let ranking_rules = get_ranking_rules_for_query_graph_search(ctx, terms_matching_strategy)?;
2023-03-18 22:04:34 +08:00
bucket_sort(ctx, ranking_rules, &graph, &universe, from, length, query_graph_logger)?
} else {
let ranking_rules = get_ranking_rules_for_placeholder_search(ctx)?;
bucket_sort(
ctx,
ranking_rules,
&PlaceholderQuery,
&universe,
from,
length,
placeholder_search_logger,
2023-03-18 22:04:34 +08:00
)?
};
Ok(SearchResult {
// TODO: correct matching words
matching_words: MatchingWords::default(),
// TODO: candidates with distinct
candidates: universe,
documents_ids,
})
}
impl<'a> Search<'a> {
// TODO
pub fn execute_new(&self) -> Result<SearchResult> {
todo!()
}
2023-03-09 18:12:31 +08:00
}