use super::source_offsets;
use crate::{StorageBackendError, StorageBackendResult, TokenTermKey};
use uqa_analysis::{AnalysisError, AnalysisResult, CompiledAnalyzer};
use uqa_core::{
memory::{Budgeted, BudgetedVec, MemoryBudget, MemoryReservation},
TokenOccurrence,
};
struct QueryBuffer<T> {
entries: BudgetedVec<T>,
keys: MemoryReservation,
}
impl<T> QueryBuffer<T> {
fn new(count: usize, budget: &MemoryBudget) -> AnalysisResult<Self> {
let mut entries = BudgetedVec::new(budget);
entries.reserve(count)?;
Ok(Self {
entries,
keys: budget.empty_reservation(),
})
}
fn push(&mut self, entry: Budgeted<T>) -> AnalysisResult<()> {
self.entries.reserve(1)?;
let (entry, memory) = entry.into_parts();
self.keys.absorb(memory);
self.entries.push(entry)?;
Ok(())
}
fn finish(self) -> Budgeted<Vec<T>> {
let (entries, mut memory) = self.entries.into_parts();
memory.absorb(self.keys);
Budgeted::new(entries, memory)
}
}
pub fn analyze_query_terms(
analyzer: &CompiledAnalyzer,
text: &str,
) -> StorageBackendResult<Vec<TokenTermKey>> {
Ok(
analyze_query_terms_budgeted(analyzer, text, &MemoryBudget::new(usize::MAX), || Ok(()))?
.into_parts()
.0,
)
}
pub fn analyze_query_terms_budgeted(
analyzer: &CompiledAnalyzer,
text: &str,
budget: &MemoryBudget,
mut poll: impl FnMut() -> AnalysisResult<()>,
) -> StorageBackendResult<Budgeted<Vec<TokenTermKey>>> {
let stream = analyzer.analyze_tokens_budgeted(text, budget, &mut poll)?;
let mut output = QueryBuffer::new(stream.tokens().len(), budget)?;
for token in stream.tokens() {
poll()?;
output.push(TokenTermKey::from_term_budgeted(
token.term(),
budget,
&mut poll,
)?)?;
}
poll()?;
Ok(output.finish())
}
pub fn analyze_query_graph(
analyzer: &CompiledAnalyzer,
text: &str,
) -> StorageBackendResult<Vec<(TokenTermKey, TokenOccurrence)>> {
Ok(
analyze_query_graph_budgeted(analyzer, text, &MemoryBudget::new(usize::MAX), || Ok(()))?
.into_parts()
.0,
)
}
pub fn analyze_query_graph_budgeted(
analyzer: &CompiledAnalyzer,
text: &str,
budget: &MemoryBudget,
mut poll: impl FnMut() -> AnalysisResult<()>,
) -> StorageBackendResult<Budgeted<Vec<(TokenTermKey, TokenOccurrence)>>> {
let stream = analyzer.analyze_tokens_budgeted(text, budget, &mut poll)?;
let mut output = QueryBuffer::new(stream.tokens().len(), budget)?;
let mut position = -1_i64;
for token in stream.tokens() {
poll()?;
position = position
.checked_add(i64::from(token.position_increment()))
.ok_or(AnalysisError::TokenPositionOverflow)?;
let occurrence = TokenOccurrence {
position: u32::try_from(position).map_err(|_| AnalysisError::TokenPositionOverflow)?,
position_length: token.position_length(),
offsets: token.offsets().map(source_offsets).transpose()?,
};
occurrence
.validate()
.map_err(|error| StorageBackendError::Other(error.to_string()))?;
let (key, memory) =
TokenTermKey::from_term_budgeted(token.term(), budget, &mut poll)?.into_parts();
output.push(Budgeted::new((key, occurrence), memory))?;
}
poll()?;
Ok(output.finish())
}
#[cfg(test)]
mod tests;