1use std::cmp::Ordering;
4use std::collections::BinaryHeap;
5
6use crate::segment::SegmentReader;
7use crate::structures::TERMINATED;
8use crate::{DocId, Result, Score};
9
10use super::Query;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct DocAddress {
17 segment_id_raw: u128,
19 pub doc_id: DocId,
21}
22
23impl DocAddress {
24 pub fn new(segment_id: u128, doc_id: DocId) -> Self {
25 Self {
26 segment_id_raw: segment_id,
27 doc_id,
28 }
29 }
30
31 pub fn segment_id(&self) -> String {
33 format!("{:032x}", self.segment_id_raw)
34 }
35
36 pub fn segment_id_u128(&self) -> Option<u128> {
38 Some(self.segment_id_raw)
39 }
40}
41
42impl serde::Serialize for DocAddress {
43 fn serialize<S: serde::Serializer>(
44 &self,
45 serializer: S,
46 ) -> std::result::Result<S::Ok, S::Error> {
47 use serde::ser::SerializeStruct;
48 let mut s = serializer.serialize_struct("DocAddress", 2)?;
49 s.serialize_field("segment_id", &format!("{:032x}", self.segment_id_raw))?;
50 s.serialize_field("doc_id", &self.doc_id)?;
51 s.end()
52 }
53}
54
55impl<'de> serde::Deserialize<'de> for DocAddress {
56 fn deserialize<D: serde::Deserializer<'de>>(
57 deserializer: D,
58 ) -> std::result::Result<Self, D::Error> {
59 #[derive(serde::Deserialize)]
60 struct Helper {
61 segment_id: String,
62 doc_id: DocId,
63 }
64 let h = Helper::deserialize(deserializer)?;
65 let raw = u128::from_str_radix(&h.segment_id, 16).map_err(serde::de::Error::custom)?;
66 Ok(DocAddress {
67 segment_id_raw: raw,
68 doc_id: h.doc_id,
69 })
70 }
71}
72
73#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
77pub struct ScoredPosition {
78 pub position: u32,
80 pub score: f32,
82}
83
84impl ScoredPosition {
85 pub fn new(position: u32, score: f32) -> Self {
86 Self { position, score }
87 }
88}
89
90#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
92pub struct SearchResult {
93 pub doc_id: DocId,
94 pub score: Score,
95 #[serde(default, skip_serializing_if = "is_zero_u128")]
97 pub segment_id: u128,
98 #[serde(default, skip_serializing_if = "Vec::is_empty")]
101 pub positions: Vec<(u32, Vec<ScoredPosition>)>,
102}
103
104fn is_zero_u128(v: &u128) -> bool {
105 *v == 0
106}
107
108pub(crate) fn compare_search_results_desc(a: &SearchResult, b: &SearchResult) -> Ordering {
110 b.score
111 .total_cmp(&a.score)
112 .then_with(|| a.segment_id.cmp(&b.segment_id))
113 .then_with(|| a.doc_id.cmp(&b.doc_id))
114}
115
116#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
118pub struct MatchedField {
119 pub field_id: u32,
121 pub ordinals: Vec<u32>,
124}
125
126impl SearchResult {
127 pub fn extract_ordinals(&self) -> Vec<MatchedField> {
131 use rustc_hash::FxHashSet;
132
133 self.positions
134 .iter()
135 .map(|(field_id, scored_positions)| {
136 let mut ordinals: FxHashSet<u32> = FxHashSet::default();
137 for sp in scored_positions {
138 let ordinal = if sp.position > 0xFFFFF {
142 sp.position >> 20
143 } else {
144 sp.position
145 };
146 ordinals.insert(ordinal);
147 }
148 let mut ordinals: Vec<u32> = ordinals.into_iter().collect();
149 ordinals.sort_unstable();
150 MatchedField {
151 field_id: *field_id,
152 ordinals,
153 }
154 })
155 .collect()
156 }
157
158 pub fn field_positions(&self, field_id: u32) -> Option<&[ScoredPosition]> {
160 self.positions
161 .iter()
162 .find(|(fid, _)| *fid == field_id)
163 .map(|(_, positions)| positions.as_slice())
164 }
165}
166
167#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
169pub struct SearchHit {
170 pub address: DocAddress,
172 pub score: Score,
173 #[serde(default, skip_serializing_if = "Vec::is_empty")]
175 pub matched_fields: Vec<MatchedField>,
176}
177
178#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
180pub struct SearchResponse {
181 pub hits: Vec<SearchHit>,
182 pub total_hits: u32,
183}
184
185impl PartialEq for SearchResult {
186 fn eq(&self, other: &Self) -> bool {
187 self.score.to_bits() == other.score.to_bits()
188 && self.segment_id == other.segment_id
189 && self.doc_id == other.doc_id
190 }
191}
192
193impl Eq for SearchResult {}
194
195impl PartialOrd for SearchResult {
196 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
197 Some(self.cmp(other))
198 }
199}
200
201impl Ord for SearchResult {
202 fn cmp(&self, other: &Self) -> Ordering {
203 other
204 .score
205 .total_cmp(&self.score)
206 .then_with(|| self.segment_id.cmp(&other.segment_id))
207 .then_with(|| self.doc_id.cmp(&other.doc_id))
208 }
209}
210
211pub trait Collector {
216 fn collect(&mut self, doc_id: DocId, score: Score, positions: &[(u32, Vec<ScoredPosition>)]);
219
220 fn would_collect(&self, _doc_id: DocId, _score: Score) -> bool {
225 true
226 }
227
228 fn collect_owned(&mut self, doc_id: DocId, score: Score, positions: super::MatchedPositions) {
231 self.collect(doc_id, score, &positions);
232 }
233
234 fn needs_positions(&self) -> bool {
236 false
237 }
238}
239
240pub struct TopKCollector {
242 heap: BinaryHeap<SearchResult>,
243 k: usize,
244 collect_positions: bool,
245 total_seen: u32,
247}
248
249const MAX_INITIAL_TOP_K_CAPACITY: usize = 8 * 1024;
254
255impl TopKCollector {
256 pub fn new(k: usize) -> Self {
257 Self {
258 heap: BinaryHeap::with_capacity(k.min(MAX_INITIAL_TOP_K_CAPACITY)),
259 k,
260 collect_positions: false,
261 total_seen: 0,
262 }
263 }
264
265 pub fn with_positions(k: usize) -> Self {
267 Self {
268 heap: BinaryHeap::with_capacity(k.min(MAX_INITIAL_TOP_K_CAPACITY)),
269 k,
270 collect_positions: true,
271 total_seen: 0,
272 }
273 }
274
275 pub fn total_seen(&self) -> u32 {
277 self.total_seen
278 }
279
280 pub fn into_sorted_results(self) -> Vec<SearchResult> {
281 let mut results: Vec<_> = self.heap.into_vec();
282 results.sort_unstable_by(compare_search_results_desc);
283 results
284 }
285
286 pub fn into_results_with_count(self) -> (Vec<SearchResult>, u32) {
288 let total = self.total_seen;
289 (self.into_sorted_results(), total)
290 }
291}
292
293impl Collector for TopKCollector {
294 fn collect(&mut self, doc_id: DocId, score: Score, positions: &[(u32, Vec<ScoredPosition>)]) {
295 self.total_seen = self.total_seen.saturating_add(1);
296
297 if !self.would_collect(doc_id, score) {
301 return;
302 }
303
304 let positions = if self.collect_positions {
305 positions.to_vec()
306 } else {
307 Vec::new()
308 };
309
310 if self.heap.len() >= self.k {
311 self.heap.pop();
312 }
313 self.heap.push(SearchResult {
314 doc_id,
315 score,
316 segment_id: 0,
317 positions,
318 });
319 }
320
321 fn would_collect(&self, doc_id: DocId, score: Score) -> bool {
322 self.k > 0
323 && (self.heap.len() < self.k
324 || self.heap.peek().is_some_and(|min| {
325 score.total_cmp(&min.score).is_gt()
326 || (score.total_cmp(&min.score).is_eq() && doc_id < min.doc_id)
327 }))
328 }
329
330 fn collect_owned(&mut self, doc_id: DocId, score: Score, positions: super::MatchedPositions) {
331 self.total_seen = self.total_seen.saturating_add(1);
332
333 if !self.would_collect(doc_id, score) {
334 return;
335 }
336
337 if self.heap.len() >= self.k {
338 self.heap.pop();
339 }
340 self.heap.push(SearchResult {
341 doc_id,
342 score,
343 segment_id: 0,
344 positions: if self.collect_positions {
345 positions
346 } else {
347 Vec::new()
348 },
349 });
350 }
351
352 fn needs_positions(&self) -> bool {
353 self.collect_positions
354 }
355}
356
357#[derive(Default)]
359pub struct CountCollector {
360 count: u64,
361}
362
363impl CountCollector {
364 pub fn new() -> Self {
365 Self { count: 0 }
366 }
367
368 pub fn count(&self) -> u64 {
370 self.count
371 }
372}
373
374impl Collector for CountCollector {
375 #[inline]
376 fn collect(
377 &mut self,
378 _doc_id: DocId,
379 _score: Score,
380 _positions: &[(u32, Vec<ScoredPosition>)],
381 ) {
382 self.count += 1;
383 }
384}
385
386pub async fn search_segment_with_count(
388 reader: &SegmentReader,
389 query: &dyn Query,
390 limit: usize,
391) -> Result<(Vec<SearchResult>, u32)> {
392 let segment_limit = limit.min(reader.num_docs() as usize);
393 let mut collector = TopKCollector::new(segment_limit);
394 collect_segment_with_limit(reader, query, &mut collector, segment_limit).await?;
395 Ok(collector.into_results_with_count())
396}
397
398pub async fn search_segment_with_positions_and_count(
400 reader: &SegmentReader,
401 query: &dyn Query,
402 limit: usize,
403) -> Result<(Vec<SearchResult>, u32)> {
404 let segment_limit = limit.min(reader.num_docs() as usize);
405 let mut collector = TopKCollector::with_positions(segment_limit);
406 collect_segment_with_limit(reader, query, &mut collector, segment_limit).await?;
407 Ok(collector.into_results_with_count())
408}
409
410fn positions_for_next_collector(
415 positions: &mut Option<super::MatchedPositions>,
416 remaining_consumers: &mut usize,
417) -> super::MatchedPositions {
418 assert!(
419 *remaining_consumers > 0,
420 "position consumer count underflow"
421 );
422 *remaining_consumers -= 1;
423 if *remaining_consumers == 0 {
424 positions
425 .take()
426 .expect("owned positions must remain for the final collector")
427 } else {
428 positions
429 .as_ref()
430 .cloned()
431 .expect("owned positions must remain while collectors are pending")
432 }
433}
434
435impl<A: Collector, B: Collector> Collector for (&mut A, &mut B) {
437 fn collect(&mut self, doc_id: DocId, score: Score, positions: &[(u32, Vec<ScoredPosition>)]) {
438 self.0.collect(doc_id, score, positions);
439 self.1.collect(doc_id, score, positions);
440 }
441 fn needs_positions(&self) -> bool {
442 self.0.needs_positions() || self.1.needs_positions()
443 }
444 fn would_collect(&self, doc_id: DocId, score: Score) -> bool {
445 (self.0.needs_positions() && self.0.would_collect(doc_id, score))
446 || (self.1.needs_positions() && self.1.would_collect(doc_id, score))
447 }
448 fn collect_owned(&mut self, doc_id: DocId, score: Score, positions: super::MatchedPositions) {
449 let wants = [
450 self.0.needs_positions() && self.0.would_collect(doc_id, score),
451 self.1.needs_positions() && self.1.would_collect(doc_id, score),
452 ];
453 let mut remaining = wants.iter().filter(|&&want| want).count();
454 let mut positions = Some(positions);
455
456 if wants[0] {
457 self.0.collect_owned(
458 doc_id,
459 score,
460 positions_for_next_collector(&mut positions, &mut remaining),
461 );
462 } else {
463 self.0.collect(doc_id, score, &[]);
464 }
465 if wants[1] {
466 self.1.collect_owned(
467 doc_id,
468 score,
469 positions_for_next_collector(&mut positions, &mut remaining),
470 );
471 } else {
472 self.1.collect(doc_id, score, &[]);
473 }
474 }
475}
476
477impl<A: Collector, B: Collector, C: Collector> Collector for (&mut A, &mut B, &mut C) {
479 fn collect(&mut self, doc_id: DocId, score: Score, positions: &[(u32, Vec<ScoredPosition>)]) {
480 self.0.collect(doc_id, score, positions);
481 self.1.collect(doc_id, score, positions);
482 self.2.collect(doc_id, score, positions);
483 }
484 fn needs_positions(&self) -> bool {
485 self.0.needs_positions() || self.1.needs_positions() || self.2.needs_positions()
486 }
487 fn would_collect(&self, doc_id: DocId, score: Score) -> bool {
488 (self.0.needs_positions() && self.0.would_collect(doc_id, score))
489 || (self.1.needs_positions() && self.1.would_collect(doc_id, score))
490 || (self.2.needs_positions() && self.2.would_collect(doc_id, score))
491 }
492 fn collect_owned(&mut self, doc_id: DocId, score: Score, positions: super::MatchedPositions) {
493 let wants = [
494 self.0.needs_positions() && self.0.would_collect(doc_id, score),
495 self.1.needs_positions() && self.1.would_collect(doc_id, score),
496 self.2.needs_positions() && self.2.would_collect(doc_id, score),
497 ];
498 let mut remaining = wants.iter().filter(|&&want| want).count();
499 let mut positions = Some(positions);
500
501 if wants[0] {
502 self.0.collect_owned(
503 doc_id,
504 score,
505 positions_for_next_collector(&mut positions, &mut remaining),
506 );
507 } else {
508 self.0.collect(doc_id, score, &[]);
509 }
510 if wants[1] {
511 self.1.collect_owned(
512 doc_id,
513 score,
514 positions_for_next_collector(&mut positions, &mut remaining),
515 );
516 } else {
517 self.1.collect(doc_id, score, &[]);
518 }
519 if wants[2] {
520 self.2.collect_owned(
521 doc_id,
522 score,
523 positions_for_next_collector(&mut positions, &mut remaining),
524 );
525 } else {
526 self.2.collect(doc_id, score, &[]);
527 }
528 }
529}
530
531pub async fn collect_segment<C: Collector>(
549 reader: &SegmentReader,
550 query: &dyn Query,
551 collector: &mut C,
552) -> Result<()> {
553 collect_segment_with_limit(reader, query, collector, usize::MAX / 2).await
555}
556
557pub async fn collect_segment_with_limit<C: Collector>(
566 reader: &SegmentReader,
567 query: &dyn Query,
568 collector: &mut C,
569 limit: usize,
570) -> Result<()> {
571 collect_segment_with_limit_seeded(reader, query, collector, limit, 0.0).await
572}
573
574pub async fn collect_segment_with_limit_seeded<C: Collector>(
579 reader: &SegmentReader,
580 query: &dyn Query,
581 collector: &mut C,
582 limit: usize,
583 initial_threshold: f32,
584) -> Result<()> {
585 let options = super::ScorerOptions {
586 collect_positions: collector.needs_positions(),
587 initial_threshold,
588 shared_threshold: None,
589 lsp_plan: None,
590 };
591 let mut scorer = query.scorer_with_options(reader, limit, options).await?;
592 drive_scorer(scorer.as_mut(), collector);
593 Ok(())
594}
595
596fn drive_scorer<C: Collector>(scorer: &mut dyn super::Scorer, collector: &mut C) {
598 let needs_positions = collector.needs_positions();
599 let mut doc = scorer.doc();
600 while doc != TERMINATED {
601 let score = scorer.score();
602 if needs_positions && collector.would_collect(doc, score) {
603 let positions = scorer.matched_positions().unwrap_or_default();
604 collector.collect_owned(doc, score, positions);
605 } else {
606 collector.collect(doc, score, &[]);
607 }
608 doc = scorer.advance();
609 }
610}
611
612#[cfg(feature = "sync")]
616pub fn search_segment_with_count_sync(
617 reader: &SegmentReader,
618 query: &dyn Query,
619 limit: usize,
620) -> Result<(Vec<SearchResult>, u32)> {
621 let segment_limit = limit.min(reader.num_docs() as usize);
622 let mut collector = TopKCollector::new(segment_limit);
623 collect_segment_with_limit_sync(reader, query, &mut collector, segment_limit)?;
624 Ok(collector.into_results_with_count())
625}
626
627#[cfg(feature = "sync")]
629pub fn search_segment_with_positions_and_count_sync(
630 reader: &SegmentReader,
631 query: &dyn Query,
632 limit: usize,
633) -> Result<(Vec<SearchResult>, u32)> {
634 let segment_limit = limit.min(reader.num_docs() as usize);
635 let mut collector = TopKCollector::with_positions(segment_limit);
636 collect_segment_with_limit_sync(reader, query, &mut collector, segment_limit)?;
637 Ok(collector.into_results_with_count())
638}
639
640#[cfg(feature = "sync")]
642pub fn collect_segment_with_limit_sync<C: Collector>(
643 reader: &SegmentReader,
644 query: &dyn Query,
645 collector: &mut C,
646 limit: usize,
647) -> Result<()> {
648 collect_segment_with_limit_seeded_sync(reader, query, collector, limit, 0.0)
649}
650
651#[cfg(feature = "sync")]
654pub fn collect_segment_with_limit_seeded_sync<C: Collector>(
655 reader: &SegmentReader,
656 query: &dyn Query,
657 collector: &mut C,
658 limit: usize,
659 initial_threshold: f32,
660) -> Result<()> {
661 let options = super::ScorerOptions {
662 collect_positions: collector.needs_positions(),
663 initial_threshold,
664 shared_threshold: None,
665 lsp_plan: None,
666 };
667 let mut scorer = query.scorer_sync_with_options(reader, limit, options)?;
668 drive_scorer(scorer.as_mut(), collector);
669 Ok(())
670}
671
672#[cfg(feature = "sync")]
679pub fn search_segment_seeded_sync(
680 reader: &SegmentReader,
681 query: &dyn Query,
682 limit: usize,
683 collect_positions: bool,
684 initial_threshold: f32,
685) -> Result<(Vec<SearchResult>, u32)> {
686 let segment_limit = limit.min(reader.num_docs() as usize);
687 let mut collector = if collect_positions {
688 TopKCollector::with_positions(segment_limit)
689 } else {
690 TopKCollector::new(segment_limit)
691 };
692 collect_segment_with_limit_seeded_sync(
693 reader,
694 query,
695 &mut collector,
696 segment_limit,
697 initial_threshold,
698 )?;
699 Ok(collector.into_results_with_count())
700}
701
702#[cfg(feature = "sync")]
704pub fn search_segment_shared_sync(
705 reader: &SegmentReader,
706 query: &dyn Query,
707 limit: usize,
708 collect_positions: bool,
709 shared_threshold: super::SharedThreshold,
710) -> Result<(Vec<SearchResult>, u32)> {
711 search_segment_shared_sync_planned(
712 reader,
713 query,
714 limit,
715 collect_positions,
716 shared_threshold,
717 None,
718 )
719}
720
721#[cfg(feature = "sync")]
723pub(crate) fn search_segment_shared_sync_planned(
724 reader: &SegmentReader,
725 query: &dyn Query,
726 limit: usize,
727 collect_positions: bool,
728 shared_threshold: super::SharedThreshold,
729 lsp_plan: Option<std::sync::Arc<super::bmp::LspSegmentPlan>>,
730) -> Result<(Vec<SearchResult>, u32)> {
731 let segment_limit = limit.min(reader.num_docs() as usize);
732 let mut collector = if collect_positions {
733 TopKCollector::with_positions(segment_limit)
734 } else {
735 TopKCollector::new(segment_limit)
736 };
737 let options = super::ScorerOptions {
738 collect_positions,
739 initial_threshold: shared_threshold.get(),
740 shared_threshold: Some(shared_threshold),
741 lsp_plan,
742 };
743 let mut scorer = query.scorer_sync_with_options(reader, segment_limit, options)?;
744 drive_scorer(scorer.as_mut(), &mut collector);
745 Ok(collector.into_results_with_count())
746}
747
748pub async fn search_segment_seeded(
750 reader: &SegmentReader,
751 query: &dyn Query,
752 limit: usize,
753 collect_positions: bool,
754 initial_threshold: f32,
755) -> Result<(Vec<SearchResult>, u32)> {
756 let segment_limit = limit.min(reader.num_docs() as usize);
757 let mut collector = if collect_positions {
758 TopKCollector::with_positions(segment_limit)
759 } else {
760 TopKCollector::new(segment_limit)
761 };
762 collect_segment_with_limit_seeded(
763 reader,
764 query,
765 &mut collector,
766 segment_limit,
767 initial_threshold,
768 )
769 .await?;
770 Ok(collector.into_results_with_count())
771}
772
773pub async fn search_segment_shared(
775 reader: &SegmentReader,
776 query: &dyn Query,
777 limit: usize,
778 collect_positions: bool,
779 shared_threshold: super::SharedThreshold,
780) -> Result<(Vec<SearchResult>, u32)> {
781 search_segment_shared_planned(
782 reader,
783 query,
784 limit,
785 collect_positions,
786 shared_threshold,
787 None,
788 )
789 .await
790}
791
792pub(crate) async fn search_segment_shared_planned(
794 reader: &SegmentReader,
795 query: &dyn Query,
796 limit: usize,
797 collect_positions: bool,
798 shared_threshold: super::SharedThreshold,
799 lsp_plan: Option<std::sync::Arc<super::bmp::LspSegmentPlan>>,
800) -> Result<(Vec<SearchResult>, u32)> {
801 let segment_limit = limit.min(reader.num_docs() as usize);
802 let mut collector = if collect_positions {
803 TopKCollector::with_positions(segment_limit)
804 } else {
805 TopKCollector::new(segment_limit)
806 };
807 let options = super::ScorerOptions {
808 collect_positions,
809 initial_threshold: shared_threshold.get(),
810 shared_threshold: Some(shared_threshold),
811 lsp_plan,
812 };
813 let mut scorer = query
814 .scorer_with_options(reader, segment_limit, options)
815 .await?;
816 drive_scorer(scorer.as_mut(), &mut collector);
817 Ok(collector.into_results_with_count())
818}
819
820#[cfg(test)]
821mod tests {
822 use super::*;
823 use std::sync::Arc;
824 use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
825
826 #[derive(Default)]
827 struct OwnedPositionCollector {
828 owned_calls: usize,
829 borrowed_calls: usize,
830 positions: super::super::MatchedPositions,
831 }
832
833 impl Collector for OwnedPositionCollector {
834 fn collect(
835 &mut self,
836 _doc_id: DocId,
837 _score: Score,
838 positions: &[(u32, Vec<ScoredPosition>)],
839 ) {
840 self.borrowed_calls += 1;
841 self.positions = positions.to_vec();
842 }
843
844 fn collect_owned(
845 &mut self,
846 _doc_id: DocId,
847 _score: Score,
848 positions: super::super::MatchedPositions,
849 ) {
850 self.owned_calls += 1;
851 self.positions = positions;
852 }
853
854 fn needs_positions(&self) -> bool {
855 true
856 }
857 }
858
859 struct PositionCountingScorer {
860 index: usize,
861 position_calls: Arc<AtomicUsize>,
862 }
863
864 impl super::super::DocSet for PositionCountingScorer {
865 fn doc(&self) -> DocId {
866 if self.index < 3 {
867 self.index as DocId
868 } else {
869 TERMINATED
870 }
871 }
872
873 fn advance(&mut self) -> DocId {
874 self.index += 1;
875 self.doc()
876 }
877
878 fn seek(&mut self, target: DocId) -> DocId {
879 self.index = target.min(3) as usize;
880 self.doc()
881 }
882
883 fn size_hint(&self) -> u32 {
884 3u32.saturating_sub(self.index as u32)
885 }
886 }
887
888 impl super::super::Scorer for PositionCountingScorer {
889 fn score(&self) -> Score {
890 [10.0, 1.0, 2.0][self.index]
891 }
892
893 fn matched_positions(&self) -> Option<super::super::MatchedPositions> {
894 self.position_calls.fetch_add(1, AtomicOrdering::Relaxed);
895 Some(vec![(7, vec![ScoredPosition::new(self.index as u32, 1.0)])])
896 }
897 }
898
899 #[test]
900 fn test_top_k_collector() {
901 let mut collector = TopKCollector::new(3);
902
903 collector.collect(0, 1.0, &[]);
904 collector.collect(1, 3.0, &[]);
905 collector.collect(2, 2.0, &[]);
906 collector.collect(3, 4.0, &[]);
907 collector.collect(4, 0.5, &[]);
908
909 let results = collector.into_sorted_results();
910
911 assert_eq!(results.len(), 3);
912 assert_eq!(results[0].doc_id, 3); assert_eq!(results[1].doc_id, 1); assert_eq!(results[2].doc_id, 2); }
916
917 #[test]
918 fn top_k_zero_retains_no_results() {
919 let mut collector = TopKCollector::new(0);
920 collector.collect(1, 1.0, &[]);
921
922 assert!(collector.into_sorted_results().is_empty());
923 }
924
925 #[test]
926 fn huge_top_k_does_not_trigger_a_huge_initial_allocation() {
927 let collector = TopKCollector::new(usize::MAX);
928
929 assert!(collector.heap.capacity() <= MAX_INITIAL_TOP_K_CAPACITY);
930 }
931
932 #[test]
933 fn positions_are_only_materialized_for_competitive_hits() {
934 let calls = Arc::new(AtomicUsize::new(0));
935 let mut scorer = PositionCountingScorer {
936 index: 0,
937 position_calls: Arc::clone(&calls),
938 };
939 let mut collector = TopKCollector::with_positions(1);
940
941 drive_scorer(&mut scorer, &mut collector);
942
943 assert_eq!(calls.load(AtomicOrdering::Relaxed), 1);
944 assert_eq!(collector.total_seen(), 3);
945 let results = collector.into_sorted_results();
946 assert_eq!(results.len(), 1);
947 assert_eq!(results[0].doc_id, 0);
948 assert_eq!(results[0].positions[0].0, 7);
949 }
950
951 #[test]
952 fn tuple_moves_owned_positions_to_single_position_collector() {
953 let mut positions = OwnedPositionCollector::default();
954 let mut count = CountCollector::new();
955 let input = vec![(7, vec![ScoredPosition::new(3, 1.0)])];
956 let input_ptr = input[0].1.as_ptr();
957
958 (&mut positions, &mut count).collect_owned(11, 2.0, input);
959
960 assert_eq!(positions.owned_calls, 1);
961 assert_eq!(positions.borrowed_calls, 0);
962 assert_eq!(positions.positions[0].1.as_ptr(), input_ptr);
963 assert_eq!(count.count(), 1);
964 }
965
966 #[test]
967 fn tuple_clones_for_all_but_final_position_collector() {
968 let mut first = OwnedPositionCollector::default();
969 let mut second = OwnedPositionCollector::default();
970 let mut count = CountCollector::new();
971 let input = vec![(7, vec![ScoredPosition::new(3, 1.0)])];
972 let input_ptr = input[0].1.as_ptr();
973
974 (&mut first, &mut count, &mut second).collect_owned(11, 2.0, input);
975
976 assert_eq!((first.owned_calls, first.borrowed_calls), (1, 0));
977 assert_eq!((second.owned_calls, second.borrowed_calls), (1, 0));
978 assert_ne!(first.positions[0].1.as_ptr(), input_ptr);
979 assert_eq!(second.positions[0].1.as_ptr(), input_ptr);
980 assert_eq!(count.count(), 1);
981 }
982
983 #[test]
984 fn test_count_collector() {
985 let mut collector = CountCollector::new();
986
987 collector.collect(0, 1.0, &[]);
988 collector.collect(1, 2.0, &[]);
989 collector.collect(2, 3.0, &[]);
990
991 assert_eq!(collector.count(), 3);
992 }
993
994 #[test]
995 fn test_multi_collector() {
996 let mut top_k = TopKCollector::new(2);
997 let mut count = CountCollector::new();
998
999 for (doc_id, score) in [(0, 1.0), (1, 3.0), (2, 2.0), (3, 4.0), (4, 0.5)] {
1001 top_k.collect(doc_id, score, &[]);
1002 count.collect(doc_id, score, &[]);
1003 }
1004
1005 assert_eq!(count.count(), 5);
1007
1008 let results = top_k.into_sorted_results();
1010 assert_eq!(results.len(), 2);
1011 assert_eq!(results[0].doc_id, 3); assert_eq!(results[1].doc_id, 1); }
1014}