use std::sync::Arc;
use laurus::Document;
use laurus::lexical::query::Query;
use laurus::lexical::{
BooleanQueryBuilder, LexicalIndexConfig, LexicalSearchRequest, LexicalStore, TermQuery,
};
use laurus::storage::Storage;
use laurus::storage::memory::{MemoryStorage, MemoryStorageConfig};
fn doc(popularity: i64) -> Document {
Document::builder()
.add_text("body", "alpha")
.add_integer("popularity", popularity)
.build()
}
const POPULARITY: [i64; 12] = [50, 51, 52, 10, 11, 12, 90, 91, 92, 30, 31, 32];
fn store_with_segments(n_segments: usize) -> (LexicalStore, Arc<dyn Storage>) {
let storage: Arc<dyn Storage> = Arc::new(MemoryStorage::new(MemoryStorageConfig::default()));
let config = LexicalIndexConfig::builder().max_segments(1000).build();
let store = LexicalStore::new(storage.clone(), config).unwrap();
let chunk = POPULARITY.len().div_ceil(n_segments);
for (group_idx, group) in POPULARITY.chunks(chunk).enumerate() {
for (offset, popularity) in group.iter().enumerate() {
let doc_id = (group_idx * chunk + offset) as u64 + 1;
store.upsert_document(doc_id, doc(*popularity)).unwrap();
}
store.commit().unwrap();
}
(store, storage)
}
fn field_sorted_ids(store: &LexicalStore, request: LexicalSearchRequest) -> Vec<u64> {
store
.search(request)
.unwrap()
.hits
.iter()
.map(|h| h.doc_id)
.collect()
}
#[test]
fn field_sort_desc_single_segment() {
let (store, _storage) = store_with_segments(1);
let query: Box<dyn Query> = Box::new(TermQuery::new("body", "alpha"));
let results = store
.search(
LexicalSearchRequest::new(query)
.limit(3)
.sort_by_field_desc("popularity"),
)
.unwrap();
assert_eq!(
results.hits.iter().map(|h| h.doc_id).collect::<Vec<_>>(),
vec![9, 8, 7]
);
assert_eq!(
results.total_hits, 12,
"total_hits must be the true match count"
);
}
#[test]
fn field_sort_asc_single_segment() {
let (store, _storage) = store_with_segments(1);
let query: Box<dyn Query> = Box::new(TermQuery::new("body", "alpha"));
let ids = field_sorted_ids(
&store,
LexicalSearchRequest::new(query)
.limit(3)
.sort_by_field_asc("popularity"),
);
assert_eq!(ids, vec![4, 5, 6]);
}
#[test]
fn field_sort_multi_segment() {
let (store, _storage) = store_with_segments(4);
let query: Box<dyn Query> = Box::new(TermQuery::new("body", "alpha"));
let ids = field_sorted_ids(
&store,
LexicalSearchRequest::new(query)
.limit(3)
.sort_by_field_desc("popularity"),
);
assert_eq!(ids, vec![9, 8, 7]);
}
#[test]
fn field_sort_parallel_boolean() {
let (store, _storage) = store_with_segments(1);
let query: Box<dyn Query> = Box::new(
BooleanQueryBuilder::new()
.should(Box::new(TermQuery::new("body", "alpha")))
.should(Box::new(TermQuery::new("body", "alpha")))
.build(),
);
let ids = field_sorted_ids(
&store,
LexicalSearchRequest::new(query)
.limit(3)
.sort_by_field_desc("popularity")
.parallel(true),
);
assert_eq!(ids, vec![9, 8, 7]);
}
#[test]
fn field_sort_with_min_score() {
let (store, _storage) = store_with_segments(1);
let query: Box<dyn Query> = Box::new(TermQuery::new("body", "alpha"));
let all = store
.search(LexicalSearchRequest::new(query.clone_box()).limit(1))
.unwrap();
let uniform_score = all.hits[0].score;
let excluded = field_sorted_ids(
&store,
LexicalSearchRequest::new(query.clone_box())
.limit(3)
.min_score(uniform_score + 1.0)
.sort_by_field_desc("popularity"),
);
assert!(excluded.is_empty());
let included = field_sorted_ids(
&store,
LexicalSearchRequest::new(query)
.limit(3)
.min_score(0.0)
.sort_by_field_desc("popularity"),
);
assert_eq!(included, vec![9, 8, 7]);
}