Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5aa20a9
perf(fts): index residual compound rows once
BubbleCal Aug 27, 2026
09f00f9
fix(fts): reject unsafe hybrid index segments
BubbleCal Aug 27, 2026
587bfe1
fix(fts): bound hybrid residual materialization
BubbleCal Aug 27, 2026
8905c13
test(fts): exercise hybrid prefilter fallback
BubbleCal Aug 27, 2026
7542e47
fix(fts): reject rewritten hybrid index sources
BubbleCal Aug 27, 2026
f8ac1ad
fix(fts): include data base in hybrid rewrite checks
BubbleCal Aug 27, 2026
d2609f3
fix(fts): reject rebound hybrid data bases
BubbleCal Aug 27, 2026
8509708
perf(fts): remove hybrid benchmark metrics
BubbleCal Aug 27, 2026
7ee4c59
fix(fts): repair hybrid integration after restack
BubbleCal Aug 27, 2026
8879063
fix(fts): address hybrid CI diagnostics
BubbleCal Aug 27, 2026
7369944
fix(fts): update hybrid scorer after metrics cleanup
BubbleCal Aug 27, 2026
8dbeea2
fix(fts): preserve physical provenance across remaps
BubbleCal Aug 28, 2026
f1bda89
fix(fts): store physical provenance in index sidecar
BubbleCal Aug 28, 2026
913e192
test(cleanup): account for FTS provenance sidecars
BubbleCal Aug 28, 2026
e49d579
test(fts): use exact BM25 scoring oracles
BubbleCal Aug 28, 2026
1800f31
test(fts): replace index for rebuilt oracle
BubbleCal Aug 28, 2026
1284b21
perf(fts): parallelize residual compound indexing
BubbleCal Aug 28, 2026
933a8b0
perf(fts): reuse tokenizer assets across residual shards
BubbleCal Aug 28, 2026
3889eca
perf(fts): use indexed stats for residual compound rows
BubbleCal Aug 30, 2026
18a0ec4
fix(fts): preserve residual term scoring
BubbleCal Aug 30, 2026
f048d57
test(fts): fix residual scorer expectations
BubbleCal Aug 30, 2026
7c30fa8
fix(fts): satisfy query-local stats visibility lint
BubbleCal Aug 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/lance-index/src/scalar/inverted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub use compound::{
compound_search, compound_search_prepared_match,
compound_search_prepared_match_with_score_floor, compound_search_with_base_scorer,
compound_search_with_base_scorer_and_score_floor, exclusive_scaled_score_floor,
materialized_compound_top_k,
};
#[doc(hidden)]
pub use cross_column::cross_column_compound_search;
Expand Down
54 changes: 54 additions & 0 deletions rust/lance-index/src/scalar/inverted/compound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2162,6 +2162,40 @@ impl TopKCollector<u64> {
}
}

/// Evaluate a compound query over exact, materialized leaf result sets.
///
/// This is the bridge used by query-local residual postings: it keeps Boolean,
/// Boost, and MultiMatch semantics in the same scorer tree as the on-disk
/// compound path while allowing a different posting source.
#[doc(hidden)]
pub fn materialized_compound_top_k(
query: &FtsQuery,
leaves: Vec<Vec<(u64, f32)>>,
limit: usize,
) -> Result<(Vec<u64>, Vec<f32>)> {
let mut leaf_count = 0;
let plan = CompoundScorerPlan::from_query(query, &mut leaf_count)?;
if leaf_count != leaves.len() {
return Err(Error::internal(format!(
"compound FTS planned {leaf_count} leaves but received {} materialized leaves",
leaves.len()
)));
}
let mut scorers = leaves
.into_iter()
.map(|rows| {
let rows = rows
.into_iter()
.map(|(row_id, score)| ScoredRow { row_id, score })
.collect();
MaterializedScorer::try_new(rows).map(|scorer| Some(Box::new(scorer) as BoxScorer<'_>))
})
.collect::<Result<Vec<_>>>()?;
let mut scorer = plan.build(&mut scorers)?;
let rows = TopKCollector::new(limit).collect(scorer.as_mut())?;
Ok(rows.into_iter().map(|row| (row.row_id, row.score)).unzip())
}

#[derive(Debug, Clone, Copy)]
pub(super) enum DisjunctionScore {
Sum,
Expand Down Expand Up @@ -4414,6 +4448,7 @@ mod tests {
use super::super::scorer::Scorer;
use super::*;
use crate::metrics::NoOpMetricsCollector;
use crate::scalar::inverted::query::MultiMatchQuery;

fn rows(values: &[(u64, f32)]) -> Vec<ScoredRow> {
values
Expand All @@ -4426,6 +4461,25 @@ mod tests {
Box::new(MaterializedScorer::try_new(rows(values)).unwrap())
}

#[test]
fn materialized_compound_top_k_preserves_multimatch_and_tie_order() {
let query = FtsQuery::MultiMatch(MultiMatchQuery {
match_queries: vec![
MatchQuery::new("alpha".to_string()).with_column(Some("text".to_string())),
MatchQuery::new("alpha".to_string()).with_column(Some("text".to_string())),
],
});
let (row_ids, scores) = materialized_compound_top_k(
&query,
vec![vec![(7, 1.0), (3, 2.0)], vec![(7, 3.0), (5, 3.0)]],
2,
)
.unwrap();

assert_eq!(row_ids, vec![5, 7]);
assert_eq!(scores, vec![3.0, 3.0]);
}

fn zero_weight_wand<'a>(
documents: &'a DocSet,
scorer: Arc<MemBM25Scorer>,
Expand Down
1 change: 1 addition & 0 deletions rust/lance/src/dataset/mem_wal/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub type RowPosition = u64;
// Re-export public types used externally
pub use btree::{BTreeIndexConfig, BTreeMemIndex};
pub use fts::{FtsIndexConfig, FtsMemIndex, FtsQueryExpr, SearchOptions};
pub(crate) use fts::{QueryLocalFtsIndex, QueryLocalFtsStats};
pub use hnsw::{HnswIndexConfig, HnswMemIndex};
pub use pk_key::encode_pk_tuple;

Expand Down
Loading
Loading