use std::sync::Arc;
use super::PhraseWeight;
use crate::query::bm25::Bm25Weight;
use crate::query::phrase_query::scoring_utils::HighlightSink;
use crate::query::{EnableScoring, Query, Weight};
use crate::schema::{Field, IndexRecordOption, Term};
#[derive(Clone, Debug)]
pub struct PhraseQuery {
field: Field,
phrase_terms: Vec<(usize, Term)>,
slop: u32,
highlight_sink: Option<Arc<HighlightSink>>,
highlight_field_name: String,
}
impl PhraseQuery {
pub fn new(terms: Vec<Term>) -> PhraseQuery {
let terms_with_offset = terms.into_iter().enumerate().collect();
PhraseQuery::new_with_offset(terms_with_offset)
}
pub fn new_with_offset(terms: Vec<(usize, Term)>) -> PhraseQuery {
PhraseQuery::new_with_offset_and_slop(terms, 0)
}
pub fn new_with_offset_and_slop(mut terms: Vec<(usize, Term)>, slop: u32) -> PhraseQuery {
assert!(
terms.len() > 1,
"A phrase query is required to have strictly more than one term."
);
terms.sort_by_key(|&(offset, _)| offset);
let field = terms[0].1.field();
assert!(
terms[1..].iter().all(|term| term.1.field() == field),
"All terms from a phrase query must belong to the same field"
);
PhraseQuery {
field,
phrase_terms: terms,
slop,
highlight_sink: None,
highlight_field_name: String::new(),
}
}
pub fn with_highlight_sink(mut self, sink: Arc<HighlightSink>, field_name: String) -> Self {
self.highlight_sink = Some(sink);
self.highlight_field_name = field_name;
self
}
pub fn set_slop(&mut self, value: u32) {
self.slop = value;
}
pub fn field(&self) -> Field {
self.field
}
pub fn phrase_terms(&self) -> Vec<Term> {
self.phrase_terms
.iter()
.map(|(_, term)| term.clone())
.collect::<Vec<Term>>()
}
pub(crate) fn phrase_weight(
&self,
enable_scoring: EnableScoring<'_>,
) -> crate::Result<PhraseWeight> {
let schema = enable_scoring.schema();
let field_entry = schema.get_field_entry(self.field);
let has_positions = field_entry
.field_type()
.get_index_record_option()
.map(IndexRecordOption::has_positions)
.unwrap_or(false);
if !has_positions {
let field_name = field_entry.name();
return Err(crate::LucivyError::SchemaError(format!(
"Applied phrase query on field {field_name:?}, which does not have positions \
indexed"
)));
}
let terms = self.phrase_terms();
let bm25_weight_opt = match enable_scoring {
EnableScoring::Enabled {
statistics_provider,
..
} => Some(Bm25Weight::for_terms(statistics_provider, &terms)?),
EnableScoring::Disabled { .. } => None,
};
let mut weight = PhraseWeight::new(self.phrase_terms.clone(), bm25_weight_opt);
if self.slop > 0 {
weight.slop(self.slop);
}
if let Some(ref sink) = self.highlight_sink {
weight = weight.with_highlight_sink(Arc::clone(sink), self.highlight_field_name.clone());
}
Ok(weight)
}
}
impl Query for PhraseQuery {
fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>> {
let phrase_weight = self.phrase_weight(enable_scoring)?;
Ok(Box::new(phrase_weight))
}
fn query_terms<'a>(&'a self, visitor: &mut dyn FnMut(&'a Term, bool)) {
for (_, term) in &self.phrase_terms {
visitor(term, true);
}
}
}