11//! This module is used when sorting the index by a property, e.g.
22//! to get mappings from old doc_id to new doc_id and vice versa, after sorting
3+ use std:: convert:: Infallible ;
4+ use std:: error:: Error ;
35
4- use common:: ReadOnlyBitSet ;
6+ use common:: { BitSet , ReadOnlyBitSet } ;
7+ use unwrap_infallible:: UnwrapInfallible ;
58
69use super :: SegmentWriter ;
710use crate :: schema:: { Field , Schema } ;
@@ -71,7 +74,34 @@ pub struct DocIdMapping {
7174}
7275
7376impl DocIdMapping {
74- pub fn from_new_id_to_old_id ( new_doc_id_to_old : Vec < DocId > ) -> Self {
77+ /// Creates a `DocIdMapping` from a mapping of new doc ids to old doc ids, with permutation
78+ /// validation. The mapping is validated by checking that every old doc id appears exactly
79+ /// once in the mapping. I.e., doc ids must be consecutive from `0` to
80+ /// `new_doc_id_to_old.len() - 1`, inclusive.
81+ pub fn new_permutation ( new_doc_id_to_old : Vec < DocId > ) -> crate :: Result < Self > {
82+ let max_doc = new_doc_id_to_old. len ( ) as DocId ;
83+ let mut seen_doc_ids: BitSet = BitSet :: with_max_value ( max_doc) ;
84+
85+ Self :: from_new_id_to_old_id_inner ( new_doc_id_to_old, |old_doc_id| {
86+ if old_doc_id >= max_doc || !seen_doc_ids. insert ( old_doc_id) {
87+ return Err ( TantivyError :: InvalidArgument (
88+ "Mapping must be a permutation of the segment doc ids" . to_string ( ) ,
89+ ) ) ;
90+ }
91+ Ok :: < ( ) , TantivyError > ( ( ) )
92+ } )
93+ }
94+
95+ /// Creates a `DocIdMapping` from a mapping of new doc ids to old doc ids.
96+ pub ( crate ) fn from_new_id_to_old_id ( new_doc_id_to_old : Vec < DocId > ) -> Self {
97+ Self :: from_new_id_to_old_id_inner ( new_doc_id_to_old, |_| Ok :: < ( ) , Infallible > ( ( ) ) )
98+ . unwrap_infallible ( )
99+ }
100+
101+ fn from_new_id_to_old_id_inner < E : Error > (
102+ new_doc_id_to_old : Vec < DocId > ,
103+ mut validate : impl FnMut ( DocId ) -> Result < ( ) , E > ,
104+ ) -> Result < Self , E > {
75105 let max_doc = new_doc_id_to_old. len ( ) ;
76106 let old_max_doc = new_doc_id_to_old
77107 . iter ( )
@@ -81,43 +111,50 @@ impl DocIdMapping {
81111 . unwrap_or ( 0 ) ;
82112 let mut old_doc_id_to_new = vec ! [ 0 ; old_max_doc as usize ] ;
83113 for i in 0 ..max_doc {
114+ ( validate) ( new_doc_id_to_old[ i] ) ?;
84115 old_doc_id_to_new[ new_doc_id_to_old[ i] as usize ] = i as DocId ;
85116 }
86- DocIdMapping {
117+ Ok ( DocIdMapping {
87118 new_doc_id_to_old,
88119 old_doc_id_to_new,
89- }
120+ } )
90121 }
91122
92- /// returns the new doc_id for the old doc_id
93- pub fn get_new_doc_id ( & self , doc_id : DocId ) -> DocId {
123+ /// Returns the new doc_id for the old doc_id
124+ pub ( crate ) fn get_new_doc_id ( & self , doc_id : DocId ) -> DocId {
94125 self . old_doc_id_to_new [ doc_id as usize ]
95126 }
96- /// returns the old doc_id for the new doc_id
97- pub fn get_old_doc_id ( & self , doc_id : DocId ) -> DocId {
98- self . new_doc_id_to_old [ doc_id as usize ]
99- }
100- /// iterate over old doc_ids in order of the new doc_ids
101- pub fn iter_old_doc_ids ( & self ) -> impl Iterator < Item = DocId > + Clone + ' _ {
102- self . new_doc_id_to_old . iter ( ) . cloned ( )
127+
128+ /// Iiterate over old doc_ids in order of the new doc_ids
129+ pub ( crate ) fn iter_old_doc_ids ( & self ) -> impl Iterator < Item = DocId > + Clone + ' _ {
130+ self . new_doc_id_to_old . iter ( ) . copied ( )
103131 }
104132
105- pub fn old_to_new_ids ( & self ) -> & [ DocId ] {
133+ /// Returns the new doc_ids in order of the old doc_ids
134+ pub ( crate ) fn old_to_new_ids ( & self ) -> & [ DocId ] {
106135 & self . old_doc_id_to_new [ ..]
107136 }
108137
109138 /// Remaps a given array to the new doc ids.
110- pub fn remap < T : Copy > ( & self , els : & [ T ] ) -> Vec < T > {
139+ pub ( crate ) fn remap < T : Copy > ( & self , els : & [ T ] ) -> Vec < T > {
111140 self . new_doc_id_to_old
112141 . iter ( )
113142 . map ( |old_doc| els[ * old_doc as usize ] )
114143 . collect ( )
115144 }
116- pub fn num_new_doc_ids ( & self ) -> usize {
145+
146+ /// Returns the number of documents in the mapping.
147+ pub ( crate ) fn len ( & self ) -> usize {
148+ // new_doc_id_to_old and old_doc_id_to_new have the same length by construction.
117149 self . new_doc_id_to_old . len ( )
118150 }
119- pub fn num_old_doc_ids ( & self ) -> usize {
120- self . old_doc_id_to_new . len ( )
151+ }
152+
153+ #[ cfg( test) ]
154+ impl DocIdMapping {
155+ /// returns the old doc_id for the new doc_id
156+ fn get_old_doc_id ( & self , doc_id : DocId ) -> DocId {
157+ self . new_doc_id_to_old [ doc_id as usize ]
121158 }
122159}
123160
@@ -155,11 +192,15 @@ mod tests_indexsorting {
155192 use common:: DateTime ;
156193
157194 use crate :: collector:: TopDocs ;
195+ use crate :: directory:: { Directory , RamDirectory } ;
196+ use crate :: index:: SegmentComponent ;
158197 use crate :: indexer:: doc_id_mapping:: DocIdMapping ;
159198 use crate :: indexer:: NoMergePolicy ;
160199 use crate :: query:: QueryParser ;
161200 use crate :: schema:: * ;
162- use crate :: { DocAddress , Index , IndexBuilder , IndexSettings , IndexSortByField , Order } ;
201+ use crate :: {
202+ DocAddress , Index , IndexBuilder , IndexSettings , IndexSortByField , Order , TantivyError ,
203+ } ;
163204
164205 fn create_test_index (
165206 index_settings : Option < IndexSettings > ,
@@ -536,6 +577,139 @@ mod tests_indexsorting {
536577 Ok ( ( ) )
537578 }
538579
580+ #[ test]
581+ fn test_single_segment_index_writer_with_doc_id_mapping ( ) -> crate :: Result < ( ) > {
582+ let mut schema_builder = Schema :: builder ( ) ;
583+ let text_field = schema_builder. add_text_field ( "text" , TEXT | STORED ) ;
584+ let schema = schema_builder. build ( ) ;
585+ let settings = IndexSettings {
586+ manual_doc_id_mapping : true ,
587+ ..Default :: default ( )
588+ } ;
589+ let mut single_segment_index_writer = Index :: builder ( )
590+ . schema ( schema)
591+ . settings ( settings)
592+ . single_segment_index_writer ( RamDirectory :: default ( ) , 15_000_000 ) ?;
593+
594+ single_segment_index_writer. add_document ( doc ! ( text_field=>"alpha beta" ) ) ?;
595+ single_segment_index_writer. add_document ( doc ! ( ) ) ?;
596+ single_segment_index_writer. add_document ( doc ! ( text_field=>"gamma" ) ) ?;
597+
598+ let mapping = DocIdMapping :: new_permutation ( vec ! [ 2 , 1 , 0 ] ) ?;
599+ let index = single_segment_index_writer. finalize_with_doc_id_mapping ( & mapping) ?;
600+
601+ let searcher = index. reader ( ) ?. searcher ( ) ;
602+ let segment_reader = searcher. segment_reader ( 0 ) ;
603+ let fieldnorm_reader = segment_reader. get_fieldnorms_reader ( text_field) ?;
604+
605+ assert_eq ! ( fieldnorm_reader. fieldnorm( 0 ) , 1 ) ;
606+ assert_eq ! ( fieldnorm_reader. fieldnorm( 1 ) , 0 ) ;
607+ assert_eq ! ( fieldnorm_reader. fieldnorm( 2 ) , 2 ) ;
608+
609+ let doc_0 = searcher. doc :: < TantivyDocument > ( DocAddress :: new ( 0 , 0 ) ) ?;
610+ assert_eq ! (
611+ doc_0. get_first( text_field) . and_then( |val| val. as_str( ) ) ,
612+ Some ( "gamma" )
613+ ) ;
614+ let doc_1 = searcher. doc :: < TantivyDocument > ( DocAddress :: new ( 0 , 1 ) ) ?;
615+ assert ! ( doc_1. get_first( text_field) . is_none( ) ) ;
616+ let doc_2 = searcher. doc :: < TantivyDocument > ( DocAddress :: new ( 0 , 2 ) ) ?;
617+ assert_eq ! (
618+ doc_2. get_first( text_field) . and_then( |val| val. as_str( ) ) ,
619+ Some ( "alpha beta" )
620+ ) ;
621+
622+ assert ! ( !index. settings( ) . manual_doc_id_mapping) ;
623+ let segment_metas = index. searchable_segment_metas ( ) ?;
624+ let segment_meta = & segment_metas[ 0 ] ;
625+ let temp_store_path = segment_meta. relative_path ( SegmentComponent :: TempStore ) ;
626+ assert ! ( !segment_meta. list_files( ) . contains( & temp_store_path) ) ;
627+ assert ! ( !index. directory( ) . exists( & temp_store_path) ?) ;
628+
629+ let mut index_writer = index. writer_for_tests ( ) ?;
630+ index_writer. add_document ( doc ! ( text_field=>"delta" ) ) ?;
631+ index_writer. commit ( ) ?;
632+
633+ Ok ( ( ) )
634+ }
635+
636+ #[ test]
637+ fn test_single_segment_index_writer_with_sort_by_field_untracks_tempstore ( ) -> crate :: Result < ( ) >
638+ {
639+ let mut schema_builder = Schema :: builder ( ) ;
640+ let sort_field = schema_builder. add_u64_field ( "sort" , FAST | STORED ) ;
641+ let schema = schema_builder. build ( ) ;
642+ let sort_field_name = schema. get_field_entry ( sort_field) . name ( ) . to_string ( ) ;
643+ let settings = IndexSettings {
644+ sort_by_field : Some ( IndexSortByField {
645+ field : sort_field_name,
646+ order : Order :: Asc ,
647+ } ) ,
648+ ..Default :: default ( )
649+ } ;
650+ let mut single_segment_index_writer = Index :: builder ( )
651+ . schema ( schema)
652+ . settings ( settings)
653+ . single_segment_index_writer ( RamDirectory :: default ( ) , 15_000_000 ) ?;
654+
655+ single_segment_index_writer. add_document ( doc ! ( sort_field=>2u64 ) ) ?;
656+ single_segment_index_writer. add_document ( doc ! ( sort_field=>1u64 ) ) ?;
657+ let index = single_segment_index_writer. finalize ( ) ?;
658+
659+ let segment_metas = index. searchable_segment_metas ( ) ?;
660+ let segment_meta = & segment_metas[ 0 ] ;
661+ let temp_store_path = segment_meta. relative_path ( SegmentComponent :: TempStore ) ;
662+ assert ! ( !segment_meta. list_files( ) . contains( & temp_store_path) ) ;
663+ assert ! ( !index. directory( ) . exists( & temp_store_path) ?) ;
664+ Ok ( ( ) )
665+ }
666+
667+ #[ test]
668+ fn test_single_segment_index_writer_finalize_rejects_manual_doc_id_mapping ( ) -> crate :: Result < ( ) >
669+ {
670+ let mut schema_builder = Schema :: builder ( ) ;
671+ let text_field = schema_builder. add_text_field ( "text" , TEXT | STORED ) ;
672+ let schema = schema_builder. build ( ) ;
673+ let settings = IndexSettings {
674+ manual_doc_id_mapping : true ,
675+ ..Default :: default ( )
676+ } ;
677+ let mut single_segment_index_writer = Index :: builder ( )
678+ . schema ( schema)
679+ . settings ( settings)
680+ . single_segment_index_writer ( RamDirectory :: default ( ) , 15_000_000 ) ?;
681+
682+ single_segment_index_writer. add_document ( doc ! ( text_field=>"alpha" ) ) ?;
683+
684+ let error = single_segment_index_writer. finalize ( ) . unwrap_err ( ) ;
685+ assert ! ( matches!( error, TantivyError :: InvalidArgument ( _) ) ) ;
686+ Ok ( ( ) )
687+ }
688+
689+ #[ test]
690+ fn test_index_builder_rejects_manual_doc_id_mapping_with_sort_by_field ( ) {
691+ let mut schema_builder = Schema :: builder ( ) ;
692+ schema_builder. add_text_field ( "text" , TEXT | STORED ) ;
693+ let sort_field = schema_builder. add_u64_field ( "sort" , STORED | FAST ) ;
694+ let schema = schema_builder. build ( ) ;
695+ let sort_field_name = schema. get_field_entry ( sort_field) . name ( ) . to_string ( ) ;
696+ let settings = IndexSettings {
697+ manual_doc_id_mapping : true ,
698+ sort_by_field : Some ( IndexSortByField {
699+ field : sort_field_name,
700+ order : Order :: Asc ,
701+ } ) ,
702+ ..Default :: default ( )
703+ } ;
704+
705+ let error = Index :: builder ( )
706+ . schema ( schema)
707+ . settings ( settings)
708+ . create_in_ram ( )
709+ . unwrap_err ( ) ;
710+ assert ! ( matches!( error, TantivyError :: InvalidArgument ( _) ) ) ;
711+ }
712+
539713 #[ test]
540714 fn test_doc_mapping ( ) {
541715 let doc_mapping = DocIdMapping :: from_new_id_to_old_id ( vec ! [ 3 , 2 , 5 ] ) ;
@@ -550,6 +724,18 @@ mod tests_indexsorting {
550724 assert_eq ! ( doc_mapping. get_new_doc_id( 5 ) , 2 ) ;
551725 }
552726
727+ #[ test]
728+ fn test_doc_mapping_new_permutation_rejects_out_of_range ( ) {
729+ let result = DocIdMapping :: new_permutation ( vec ! [ 5 , 0 ] ) ;
730+ assert ! ( matches!( result, Err ( TantivyError :: InvalidArgument ( _) ) , ) ) ;
731+ }
732+
733+ #[ test]
734+ fn test_doc_mapping_new_permutation_rejects_duplicates ( ) {
735+ let result = DocIdMapping :: new_permutation ( vec ! [ 0 , 1 , 0 ] ) ;
736+ assert ! ( matches!( result, Err ( TantivyError :: InvalidArgument ( _) ) , ) ) ;
737+ }
738+
553739 #[ test]
554740 fn test_doc_mapping_remap ( ) {
555741 let doc_mapping = DocIdMapping :: from_new_id_to_old_id ( vec ! [ 2 , 8 , 3 ] ) ;
0 commit comments