mod cursor;
mod flat;
mod search;
mod stats;
#[cfg(test)]
mod testing;
use std::sync::Arc;
use lance_core::{Error, Result};
use lance_select::RowAddrTreeMap;
pub use flat::flat_combined_fields_search_stream;
pub use search::combined_fields_search;
pub use stats::{CombinedCorpusStats, FlatFieldStats, build_combined_bm25_scorer};
use super::index::InvertedIndex;
use super::query::Tokens;
pub struct CombinedFieldColumn {
pub column: String,
pub weight: f32,
pub indices: Vec<Arc<InvertedIndex>>,
pub stale_rows: Option<Arc<RowAddrTreeMap>>,
}
fn unique_terms(tokens: &Tokens) -> Vec<String> {
let mut terms = Vec::with_capacity(tokens.len());
let mut seen = std::collections::HashSet::new();
for token in tokens {
if seen.insert(token.as_str()) {
terms.push(token.clone());
}
}
terms
}
pub fn validate_combined_tokenizers(columns: &[CombinedFieldColumn]) -> Result<()> {
let mut indexed = columns
.iter()
.filter_map(|column| Some((column, column.indices.first()?)));
let Some((reference, reference_index)) = indexed.next() else {
return Ok(());
};
let offending: Vec<&str> = indexed
.filter(|(_, index)| !reference_index.params().same_tokenization(index.params()))
.map(|(column, _)| column.column.as_str())
.collect();
if !offending.is_empty() {
return Err(Error::invalid_input(format!(
"combined_fields requires every target column to share the same tokenizer/index \
configuration; column(s) {:?} differ from column '{}'",
offending, reference.column
)));
}
Ok(())
}