@@ -15,17 +15,8 @@ use crate::core::json_utils::{
15
15
use crate :: core:: Index ;
16
16
use crate :: query:: range_query:: { is_type_valid_for_fastfield_range_query, RangeQuery } ;
17
17
use crate :: query:: {
18
- AllQuery ,
19
- BooleanQuery ,
20
- BoostQuery ,
21
- EmptyQuery ,
22
- FuzzyTermQuery ,
23
- Occur ,
24
- PhraseQuery ,
25
- Query ,
26
- // RangeQuery,
27
- TermQuery ,
28
- TermSetQuery ,
18
+ AllQuery , BooleanQuery , BoostQuery , EmptyQuery , FuzzyTermQuery , Occur , PhrasePrefixQuery ,
19
+ PhraseQuery , Query , TermQuery , TermSetQuery ,
29
20
} ;
30
21
use crate :: schema:: {
31
22
Facet , FacetParseError , Field , FieldType , IndexRecordOption , IntoIpv6Addr , JsonObjectOptions ,
@@ -194,6 +185,10 @@ fn trim_ast(logical_ast: LogicalAst) -> Option<LogicalAst> {
194
185
///
195
186
/// Phrase terms support the `~` slop operator which allows to set the phrase's matching
196
187
/// distance in words. `"big wolf"~1` will return documents containing the phrase `"big bad wolf"`.
188
+ ///
189
+ /// Phrase terms also support the `*` prefix operator which switches the phrase's matching
190
+ /// to consider all documents which contain the last term as a prefix, e.g. `"big bad wo"*` will
191
+ /// match `"big bad wolf"`.
197
192
#[ derive( Clone ) ]
198
193
pub struct QueryParser {
199
194
schema : Schema ,
@@ -446,6 +441,7 @@ impl QueryParser {
446
441
json_path : & str ,
447
442
phrase : & str ,
448
443
slop : u32 ,
444
+ prefix : bool ,
449
445
) -> Result < Vec < LogicalLiteral > , QueryParserError > {
450
446
let field_entry = self . schema . get_field_entry ( field) ;
451
447
let field_type = field_entry. field_type ( ) ;
@@ -503,6 +499,7 @@ impl QueryParser {
503
499
field,
504
500
phrase,
505
501
slop,
502
+ prefix,
506
503
& text_analyzer,
507
504
index_record_option,
508
505
) ?
@@ -661,9 +658,13 @@ impl QueryParser {
661
658
self . compute_path_triplets_for_literal ( & literal) ?;
662
659
let mut asts: Vec < LogicalAst > = Vec :: new ( ) ;
663
660
for ( field, json_path, phrase) in term_phrases {
664
- for ast in
665
- self . compute_logical_ast_for_leaf ( field, json_path, phrase, literal. slop ) ?
666
- {
661
+ for ast in self . compute_logical_ast_for_leaf (
662
+ field,
663
+ json_path,
664
+ phrase,
665
+ literal. slop ,
666
+ literal. prefix ,
667
+ ) ? {
667
668
// Apply some field specific boost defined at the query parser level.
668
669
let boost = self . field_boost ( field) ;
669
670
asts. push ( LogicalAst :: Leaf ( Box :: new ( ast) ) . boost ( boost) ) ;
@@ -753,9 +754,17 @@ fn convert_literal_to_query(
753
754
Box :: new ( TermQuery :: new ( term, IndexRecordOption :: WithFreqs ) )
754
755
}
755
756
}
756
- LogicalLiteral :: Phrase ( term_with_offsets, slop) => Box :: new (
757
- PhraseQuery :: new_with_offset_and_slop ( term_with_offsets, slop) ,
758
- ) ,
757
+ LogicalLiteral :: Phrase {
758
+ terms,
759
+ slop,
760
+ prefix,
761
+ } => {
762
+ if prefix {
763
+ Box :: new ( PhrasePrefixQuery :: new_with_offset ( terms) )
764
+ } else {
765
+ Box :: new ( PhraseQuery :: new_with_offset_and_slop ( terms, slop) )
766
+ }
767
+ }
759
768
LogicalLiteral :: Range {
760
769
field,
761
770
value_type,
@@ -774,6 +783,7 @@ fn generate_literals_for_str(
774
783
field : Field ,
775
784
phrase : & str ,
776
785
slop : u32 ,
786
+ prefix : bool ,
777
787
text_analyzer : & TextAnalyzer ,
778
788
index_record_option : IndexRecordOption ,
779
789
) -> Result < Option < LogicalLiteral > , QueryParserError > {
@@ -795,7 +805,11 @@ fn generate_literals_for_str(
795
805
field_name. to_string ( ) ,
796
806
) ) ;
797
807
}
798
- Ok ( Some ( LogicalLiteral :: Phrase ( terms, slop) ) )
808
+ Ok ( Some ( LogicalLiteral :: Phrase {
809
+ terms,
810
+ slop,
811
+ prefix,
812
+ } ) )
799
813
}
800
814
801
815
fn generate_literals_for_json_object (
@@ -841,7 +855,11 @@ fn generate_literals_for_json_object(
841
855
field_name. to_string ( ) ,
842
856
) ) ;
843
857
}
844
- logical_literals. push ( LogicalLiteral :: Phrase ( terms, 0 ) ) ;
858
+ logical_literals. push ( LogicalLiteral :: Phrase {
859
+ terms,
860
+ slop : 0 ,
861
+ prefix : false ,
862
+ } ) ;
845
863
Ok ( logical_literals)
846
864
}
847
865
@@ -1643,6 +1661,27 @@ mod test {
1643
1661
) ;
1644
1662
}
1645
1663
1664
+ #[ test]
1665
+ pub fn test_phrase_prefix ( ) {
1666
+ test_parse_query_to_logical_ast_helper (
1667
+ "\" big bad wo\" *" ,
1668
+ r#"("[(0, Term(field=0, type=Str, "big")), (1, Term(field=0, type=Str, "bad")), (2, Term(field=0, type=Str, "wo"))]"* "[(0, Term(field=1, type=Str, "big")), (1, Term(field=1, type=Str, "bad")), (2, Term(field=1, type=Str, "wo"))]"*)"# ,
1669
+ false ,
1670
+ ) ;
1671
+
1672
+ let query_parser = make_query_parser ( ) ;
1673
+ let query = query_parser. parse_query ( "\" big bad wo\" *" ) . unwrap ( ) ;
1674
+ assert_eq ! (
1675
+ format!( "{query:?}" ) ,
1676
+ "BooleanQuery { subqueries: [(Should, PhrasePrefixQuery { field: Field(0), \
1677
+ phrase_terms: [(0, Term(field=0, type=Str, \" big\" )), (1, Term(field=0, type=Str, \
1678
+ \" bad\" ))], prefix: (2, Term(field=0, type=Str, \" wo\" )), max_expansions: 50 }), \
1679
+ (Should, PhrasePrefixQuery { field: Field(1), phrase_terms: [(0, Term(field=1, \
1680
+ type=Str, \" big\" )), (1, Term(field=1, type=Str, \" bad\" ))], prefix: (2, \
1681
+ Term(field=1, type=Str, \" wo\" )), max_expansions: 50 })] }"
1682
+ ) ;
1683
+ }
1684
+
1646
1685
#[ test]
1647
1686
pub fn test_term_set_query ( ) {
1648
1687
test_parse_query_to_logical_ast_helper (
0 commit comments