1use std::net::{AddrParseError, IpAddr};
2use std::num::{ParseFloatError, ParseIntError};
3use std::ops::Bound;
4use std::str::{FromStr, ParseBoolError};
5
6use base64::engine::general_purpose::STANDARD as BASE64;
7use base64::Engine;
8use itertools::Itertools;
9use query_grammar::{UserInputAst, UserInputBound, UserInputLeaf, UserInputLiteral};
10use rustc_hash::FxHashMap;
11
12use super::logical_ast::*;
13use crate::index::Index;
14use crate::json_utils::convert_to_fast_value_and_append_to_json_term;
15use crate::query::range_query::{is_type_valid_for_fastfield_range_query, RangeQuery};
16use crate::query::{
17 AllQuery, BooleanQuery, BoostQuery, EmptyQuery, FuzzyTermQuery, Occur, PhrasePrefixQuery,
18 PhraseQuery, Query, TermQuery, TermSetQuery,
19};
20use crate::schema::{
21 Facet, FacetParseError, Field, FieldType, IndexRecordOption, IntoIpv6Addr, JsonObjectOptions,
22 Schema, Term, TextFieldIndexing, Type,
23};
24use crate::time::format_description::well_known::Rfc3339;
25use crate::time::OffsetDateTime;
26use crate::tokenizer::{TextAnalyzer, TokenizerManager};
27use crate::{DateTime, Score};
28
29#[derive(Debug, PartialEq, Eq, Error)]
31pub enum QueryParserError {
32 #[error("Syntax Error: {0}")]
34 SyntaxError(String),
35 #[error("Unsupported query: {0}")]
37 UnsupportedQuery(String),
38 #[error("Field does not exist: '{0}'")]
40 FieldDoesNotExist(String),
41 #[error("Expected a valid integer: '{0:?}'")]
44 ExpectedInt(#[from] ParseIntError),
45 #[error("Expected base64: '{0:?}'")]
48 ExpectedBase64(#[from] base64::DecodeError),
49 #[error("Invalid query: Only excluding terms given")]
52 ExpectedFloat(#[from] ParseFloatError),
53 #[error("Expected a bool value: '{0:?}'")]
56 ExpectedBool(#[from] ParseBoolError),
57 #[error("Invalid query: Only excluding terms given")]
59 AllButQueryForbidden,
60 #[error("No default field declared and no field specified in query")]
63 NoDefaultFieldDeclared,
64 #[error("The field '{0}' is not declared as indexed")]
67 FieldNotIndexed(String),
68 #[error("The field '{0}' does not have positions indexed")]
71 FieldDoesNotHavePositionsIndexed(String),
72 #[error(
74 "The phrase '{phrase:?}' does not produce at least two terms using the tokenizer \
75 '{tokenizer:?}'"
76 )]
77 PhrasePrefixRequiresAtLeastTwoTerms {
78 phrase: String,
80 tokenizer: String,
82 },
83 #[error("The tokenizer '{tokenizer:?}' for the field '{field:?}' is unknown")]
86 UnknownTokenizer {
87 tokenizer: String,
89 field: String,
91 },
92 #[error("A range query cannot have a phrase as one of the bounds")]
95 RangeMustNotHavePhrase,
96 #[error("The date field has an invalid format")]
98 DateFormatError(#[from] time::error::Parse),
99 #[error("The facet field is malformed: {0}")]
101 FacetFormatError(#[from] FacetParseError),
102 #[error("The ip field is malformed: {0}")]
104 IpFormatError(#[from] AddrParseError),
105}
106
107fn trim_ast(logical_ast: LogicalAst) -> Option<LogicalAst> {
111 match logical_ast {
112 LogicalAst::Clause(children) => {
113 let trimmed_children = children
114 .into_iter()
115 .flat_map(|(occur, child)| {
116 trim_ast(child).map(|trimmed_child| (occur, trimmed_child))
117 })
118 .collect::<Vec<_>>();
119 if trimmed_children.is_empty() {
120 None
121 } else {
122 Some(LogicalAst::Clause(trimmed_children))
123 }
124 }
125 _ => Some(logical_ast),
126 }
127}
128
129#[derive(Clone)]
202pub struct QueryParser {
203 schema: Schema,
204 default_fields: Vec<Field>,
205 conjunction_by_default: bool,
206 tokenizer_manager: TokenizerManager,
207 boost: FxHashMap<Field, Score>,
208 fuzzy: FxHashMap<Field, Fuzzy>,
209}
210
211#[derive(Clone)]
212struct Fuzzy {
213 prefix: bool,
214 distance: u8,
215 transpose_cost_one: bool,
216}
217
218fn all_negative(ast: &LogicalAst) -> bool {
219 match ast {
220 LogicalAst::Leaf(_) => false,
221 LogicalAst::Boost(ref child_ast, _) => all_negative(child_ast),
222 LogicalAst::Clause(children) => children
223 .iter()
224 .all(|(ref occur, child)| (*occur == Occur::MustNot) || all_negative(child)),
225 }
226}
227
228fn make_non_negative(ast: &mut LogicalAst) {
230 match ast {
231 LogicalAst::Leaf(_) => (),
232 LogicalAst::Boost(ref mut child_ast, _) => make_non_negative(child_ast),
233 LogicalAst::Clause(children) => children.push((Occur::Should, LogicalLiteral::All.into())),
234 }
235}
236
237macro_rules! try_tuple {
239 ($expr:expr) => {{
240 match $expr {
241 Ok(val) => val,
242 Err(e) => return (None, vec![e.into()]),
243 }
244 }};
245}
246
247impl QueryParser {
248 pub fn new(
252 schema: Schema,
253 default_fields: Vec<Field>,
254 tokenizer_manager: TokenizerManager,
255 ) -> QueryParser {
256 QueryParser {
257 schema,
258 default_fields,
259 tokenizer_manager,
260 conjunction_by_default: false,
261 boost: Default::default(),
262 fuzzy: Default::default(),
263 }
264 }
265
266 pub(crate) fn split_full_path<'a>(&self, full_path: &'a str) -> Option<(Field, &'a str)> {
269 self.schema.find_field(full_path)
270 }
271
272 pub fn for_index(index: &Index, default_fields: Vec<Field>) -> QueryParser {
276 QueryParser::new(index.schema(), default_fields, index.tokenizers().clone())
277 }
278
279 pub fn set_conjunction_by_default(&mut self) {
285 self.conjunction_by_default = true;
286 }
287
288 pub fn set_field_boost(&mut self, field: Field, boost: Score) {
296 self.boost.insert(field, boost);
297 }
298
299 pub fn set_field_fuzzy(
307 &mut self,
308 field: Field,
309 prefix: bool,
310 distance: u8,
311 transpose_cost_one: bool,
312 ) {
313 self.fuzzy.insert(
314 field,
315 Fuzzy {
316 prefix,
317 distance,
318 transpose_cost_one,
319 },
320 );
321 }
322
323 pub fn parse_query(&self, query: &str) -> Result<Box<dyn Query>, QueryParserError> {
328 let logical_ast = self.parse_query_to_logical_ast(query)?;
329 Ok(convert_to_query(&self.fuzzy, logical_ast))
330 }
331
332 pub fn parse_query_lenient(&self, query: &str) -> (Box<dyn Query>, Vec<QueryParserError>) {
341 let (logical_ast, errors) = self.parse_query_to_logical_ast_lenient(query);
342 (convert_to_query(&self.fuzzy, logical_ast), errors)
343 }
344
345 pub fn build_query_from_user_input_ast(
351 &self,
352 user_input_ast: UserInputAst,
353 ) -> Result<Box<dyn Query>, QueryParserError> {
354 let (logical_ast, mut err) = self.compute_logical_ast_lenient(user_input_ast);
355 if !err.is_empty() {
356 return Err(err.swap_remove(0));
357 }
358 Ok(convert_to_query(&self.fuzzy, logical_ast))
359 }
360
361 pub fn build_query_from_user_input_ast_lenient(
365 &self,
366 user_input_ast: UserInputAst,
367 ) -> (Box<dyn Query>, Vec<QueryParserError>) {
368 let (logical_ast, errors) = self.compute_logical_ast_lenient(user_input_ast);
369 (convert_to_query(&self.fuzzy, logical_ast), errors)
370 }
371
372 fn parse_query_to_logical_ast(&self, query: &str) -> Result<LogicalAst, QueryParserError> {
374 let user_input_ast = query_grammar::parse_query(query)
375 .map_err(|_| QueryParserError::SyntaxError(query.to_string()))?;
376 let (ast, mut err) = self.compute_logical_ast_lenient(user_input_ast);
377 if !err.is_empty() {
378 return Err(err.swap_remove(0));
379 }
380 Ok(ast.simplify())
381 }
382
383 fn parse_query_to_logical_ast_lenient(
385 &self,
386 query: &str,
387 ) -> (LogicalAst, Vec<QueryParserError>) {
388 let (user_input_ast, errors) = query_grammar::parse_query_lenient(query);
389 let mut errors: Vec<_> = errors
390 .into_iter()
391 .map(|error| {
392 QueryParserError::SyntaxError(format!(
393 "{} at position {}",
394 error.message, error.pos
395 ))
396 })
397 .collect();
398 let (ast, mut ast_errors) = self.compute_logical_ast_lenient(user_input_ast);
399 errors.append(&mut ast_errors);
400 (ast, errors)
401 }
402
403 fn compute_logical_ast_lenient(
404 &self,
405 user_input_ast: UserInputAst,
406 ) -> (LogicalAst, Vec<QueryParserError>) {
407 let (mut ast, mut err) = self.compute_logical_ast_with_occur_lenient(user_input_ast);
408 if let LogicalAst::Clause(children) = &ast {
409 if children.is_empty() {
410 return (ast, err);
411 }
412 }
413 if all_negative(&ast) {
414 err.push(QueryParserError::AllButQueryForbidden);
415 make_non_negative(&mut ast);
416 }
417 (ast, err)
418 }
419
420 fn compute_boundary_term(
421 &self,
422 field: Field,
423 json_path: &str,
424 phrase: &str,
425 ) -> Result<Term, QueryParserError> {
426 let field_entry = self.schema.get_field_entry(field);
427 let field_type = field_entry.field_type();
428 let field_supports_ff_range_queries = field_type.is_fast()
429 && is_type_valid_for_fastfield_range_query(field_type.value_type());
430
431 if !field_type.is_indexed() && !field_supports_ff_range_queries {
432 return Err(QueryParserError::FieldNotIndexed(
433 field_entry.name().to_string(),
434 ));
435 }
436 if !json_path.is_empty() && field_type.value_type() != Type::Json {
437 return Err(QueryParserError::UnsupportedQuery(format!(
438 "Json path is not supported for field {:?}",
439 field_entry.name()
440 )));
441 }
442 match *field_type {
443 FieldType::U64(_) => {
444 let val: u64 = u64::from_str(phrase)?;
445 Ok(Term::from_field_u64(field, val))
446 }
447 FieldType::I64(_) => {
448 let val: i64 = i64::from_str(phrase)?;
449 Ok(Term::from_field_i64(field, val))
450 }
451 FieldType::F64(_) => {
452 let val: f64 = f64::from_str(phrase)?;
453 Ok(Term::from_field_f64(field, val))
454 }
455 FieldType::Bool(_) => {
456 let val: bool = bool::from_str(phrase)?;
457 Ok(Term::from_field_bool(field, val))
458 }
459 FieldType::Date(_) => {
460 let dt = OffsetDateTime::parse(phrase, &Rfc3339)?;
461 Ok(Term::from_field_date(field, DateTime::from_utc(dt)))
462 }
463 FieldType::Str(ref str_options) => {
464 let option = str_options.get_indexing_options().ok_or_else(|| {
465 QueryParserError::FieldNotIndexed(field_entry.name().to_string())
467 })?;
468 let mut text_analyzer =
469 self.tokenizer_manager
470 .get(option.tokenizer())
471 .ok_or_else(|| QueryParserError::UnknownTokenizer {
472 field: field_entry.name().to_string(),
473 tokenizer: option.tokenizer().to_string(),
474 })?;
475 let mut terms: Vec<Term> = Vec::new();
476 let mut token_stream = text_analyzer.token_stream(phrase);
477 token_stream.process(&mut |token| {
478 let term = Term::from_field_text(field, &token.text);
479 terms.push(term);
480 });
481 if terms.len() != 1 {
482 return Err(QueryParserError::UnsupportedQuery(format!(
483 "Range query boundary cannot have multiple tokens: {phrase:?} [{terms:?}]."
484 )));
485 }
486 Ok(terms.into_iter().next().unwrap())
487 }
488 FieldType::JsonObject(ref json_options) => {
489 let get_term_with_path = || {
490 Term::from_field_json_path(
491 field,
492 json_path,
493 json_options.is_expand_dots_enabled(),
494 )
495 };
496 if let Some(term) =
497 convert_to_fast_value_and_append_to_json_term(
499 get_term_with_path(),
500 phrase,
501 false,
502 )
503 {
504 Ok(term)
505 } else {
506 let mut term = get_term_with_path();
507 term.append_type_and_str(phrase);
508 Ok(term)
509 }
510 }
511 FieldType::Facet(_) => match Facet::from_text(phrase) {
512 Ok(facet) => Ok(Term::from_facet(field, &facet)),
513 Err(e) => Err(QueryParserError::from(e)),
514 },
515 FieldType::Bytes(_) => {
516 let bytes = BASE64
517 .decode(phrase)
518 .map_err(QueryParserError::ExpectedBase64)?;
519 Ok(Term::from_field_bytes(field, &bytes))
520 }
521 FieldType::IpAddr(_) => {
522 let ip_v6 = IpAddr::from_str(phrase)?.into_ipv6_addr();
523 Ok(Term::from_field_ip_addr(field, ip_v6))
524 }
525 }
526 }
527
528 fn compute_logical_ast_for_leaf(
529 &self,
530 field: Field,
531 json_path: &str,
532 phrase: &str,
533 slop: u32,
534 prefix: bool,
535 ) -> Result<Vec<LogicalLiteral>, QueryParserError> {
536 let field_entry = self.schema.get_field_entry(field);
537 let field_type = field_entry.field_type();
538 let field_name = field_entry.name();
539 if !field_type.is_indexed() {
540 return Err(QueryParserError::FieldNotIndexed(field_name.to_string()));
541 }
542 if field_type.value_type() != Type::Json && !json_path.is_empty() {
543 let field_name = self.schema.get_field_name(field);
544 return Err(QueryParserError::FieldDoesNotExist(format!(
545 "{field_name}.{json_path}"
546 )));
547 }
548 match *field_type {
549 FieldType::U64(_) => {
550 let val: u64 = u64::from_str(phrase)?;
551 let i64_term = Term::from_field_u64(field, val);
552 Ok(vec![LogicalLiteral::Term(i64_term)])
553 }
554 FieldType::I64(_) => {
555 let val: i64 = i64::from_str(phrase)?;
556 let i64_term = Term::from_field_i64(field, val);
557 Ok(vec![LogicalLiteral::Term(i64_term)])
558 }
559 FieldType::F64(_) => {
560 let val: f64 = f64::from_str(phrase)?;
561 let f64_term = Term::from_field_f64(field, val);
562 Ok(vec![LogicalLiteral::Term(f64_term)])
563 }
564 FieldType::Bool(_) => {
565 let val: bool = bool::from_str(phrase)?;
566 let bool_term = Term::from_field_bool(field, val);
567 Ok(vec![LogicalLiteral::Term(bool_term)])
568 }
569 FieldType::Date(_) => {
570 let dt = OffsetDateTime::parse(phrase, &Rfc3339)?;
571 let dt_term = Term::from_field_date_for_search(field, DateTime::from_utc(dt));
572 Ok(vec![LogicalLiteral::Term(dt_term)])
573 }
574 FieldType::Str(ref str_options) => {
575 let indexing_options = str_options.get_indexing_options().ok_or_else(|| {
576 QueryParserError::FieldNotIndexed(field_name.to_string())
578 })?;
579 let mut text_analyzer = self
580 .tokenizer_manager
581 .get(indexing_options.tokenizer())
582 .ok_or_else(|| QueryParserError::UnknownTokenizer {
583 field: field_name.to_string(),
584 tokenizer: indexing_options.tokenizer().to_string(),
585 })?;
586 Ok(generate_literals_for_str(
587 field_name,
588 field,
589 phrase,
590 slop,
591 prefix,
592 indexing_options,
593 &mut text_analyzer,
594 )?
595 .into_iter()
596 .collect())
597 }
598 FieldType::JsonObject(ref json_options) => generate_literals_for_json_object(
599 field_name,
600 field,
601 json_path,
602 phrase,
603 &self.tokenizer_manager,
604 json_options,
605 ),
606 FieldType::Facet(_) => match Facet::from_text(phrase) {
607 Ok(facet) => {
608 let facet_term = Term::from_facet(field, &facet);
609 Ok(vec![LogicalLiteral::Term(facet_term)])
610 }
611 Err(e) => Err(QueryParserError::from(e)),
612 },
613 FieldType::Bytes(_) => {
614 let bytes = BASE64
615 .decode(phrase)
616 .map_err(QueryParserError::ExpectedBase64)?;
617 let bytes_term = Term::from_field_bytes(field, &bytes);
618 Ok(vec![LogicalLiteral::Term(bytes_term)])
619 }
620 FieldType::IpAddr(_) => {
621 let ip_v6 = IpAddr::from_str(phrase)?.into_ipv6_addr();
622 let term = Term::from_field_ip_addr(field, ip_v6);
623 Ok(vec![LogicalLiteral::Term(term)])
624 }
625 }
626 }
627
628 fn default_occur(&self) -> Occur {
629 if self.conjunction_by_default {
630 Occur::Must
631 } else {
632 Occur::Should
633 }
634 }
635
636 fn resolve_bound(
637 &self,
638 field: Field,
639 json_path: &str,
640 bound: &UserInputBound,
641 ) -> Result<Bound<Term>, QueryParserError> {
642 if bound.term_str() == "*" {
643 return Ok(Bound::Unbounded);
644 }
645 let term = self.compute_boundary_term(field, json_path, bound.term_str())?;
646 match *bound {
647 UserInputBound::Inclusive(_) => Ok(Bound::Included(term)),
648 UserInputBound::Exclusive(_) => Ok(Bound::Excluded(term)),
649 UserInputBound::Unbounded => Ok(Bound::Unbounded),
650 }
651 }
652
653 fn compute_logical_ast_with_occur_lenient(
654 &self,
655 user_input_ast: UserInputAst,
656 ) -> (LogicalAst, Vec<QueryParserError>) {
657 match user_input_ast {
658 UserInputAst::Clause(sub_queries) => {
659 let default_occur = self.default_occur();
660 let mut logical_sub_queries: Vec<(Occur, LogicalAst)> = Vec::new();
661 let mut errors = Vec::new();
662 for (occur_opt, sub_ast) in sub_queries {
663 let (sub_ast, mut sub_errors) =
664 self.compute_logical_ast_with_occur_lenient(sub_ast);
665 let occur = occur_opt.unwrap_or(default_occur);
666 logical_sub_queries.push((occur, sub_ast));
667 errors.append(&mut sub_errors);
668 }
669 (LogicalAst::Clause(logical_sub_queries), errors)
670 }
671 UserInputAst::Boost(ast, boost) => {
672 let (ast, errors) = self.compute_logical_ast_with_occur_lenient(*ast);
673 (ast.boost(boost as Score), errors)
674 }
675 UserInputAst::Leaf(leaf) => {
676 let (ast, errors) = self.compute_logical_ast_from_leaf_lenient(*leaf);
677 (
680 ast.unwrap_or_else(|| LogicalAst::Clause(Vec::new())),
681 errors,
682 )
683 }
684 }
685 }
686
687 fn field_boost(&self, field: Field) -> Score {
688 self.boost.get(&field).cloned().unwrap_or(1.0)
689 }
690
691 fn default_indexed_json_fields(&self) -> impl Iterator<Item = Field> + '_ {
692 let schema = self.schema.clone();
693 self.default_fields.iter().cloned().filter(move |field| {
694 let field_type = schema.get_field_entry(*field).field_type();
695 field_type.value_type() == Type::Json && field_type.is_indexed()
696 })
697 }
698
699 fn compute_path_triplets_for_literal<'a>(
720 &self,
721 literal: &'a UserInputLiteral,
722 ) -> Result<Vec<(Field, &'a str, &'a str)>, QueryParserError> {
723 let full_path = if let Some(full_path) = &literal.field_name {
724 full_path
725 } else {
726 if self.default_fields.is_empty() {
729 return Err(QueryParserError::NoDefaultFieldDeclared);
730 }
731 return Ok(self
732 .default_fields
733 .iter()
734 .map(|default_field| (*default_field, "", literal.phrase.as_str()))
735 .collect::<Vec<(Field, &str, &str)>>());
736 };
737 if let Some((field, path)) = self.split_full_path(full_path) {
738 return Ok(vec![(field, path, literal.phrase.as_str())]);
739 }
740 let triplets: Vec<(Field, &str, &str)> = self
742 .default_indexed_json_fields()
743 .map(|json_field| (json_field, full_path.as_str(), literal.phrase.as_str()))
744 .collect();
745 if triplets.is_empty() {
746 return Err(QueryParserError::FieldDoesNotExist(full_path.to_string()));
747 }
748 Ok(triplets)
749 }
750
751 fn compute_logical_ast_from_leaf_lenient(
752 &self,
753 leaf: UserInputLeaf,
754 ) -> (Option<LogicalAst>, Vec<QueryParserError>) {
755 match leaf {
756 UserInputLeaf::Literal(literal) => {
757 let term_phrases: Vec<(Field, &str, &str)> =
758 try_tuple!(self.compute_path_triplets_for_literal(&literal));
759 let mut asts: Vec<LogicalAst> = Vec::new();
760 let mut errors: Vec<QueryParserError> = Vec::new();
761 for (field, json_path, phrase) in term_phrases {
762 let unboosted_asts = match self.compute_logical_ast_for_leaf(
763 field,
764 json_path,
765 phrase,
766 literal.slop,
767 literal.prefix,
768 ) {
769 Ok(asts) => asts,
770 Err(e) => {
771 errors.push(e);
772 continue;
773 }
774 };
775 for ast in unboosted_asts {
776 let boost = self.field_boost(field);
778 asts.push(LogicalAst::Leaf(Box::new(ast)).boost(boost));
779 }
780 }
781 if !asts.is_empty() {
782 errors.clear();
786 }
787 let result_ast: LogicalAst = if asts.len() == 1 {
788 asts.into_iter().next().unwrap()
789 } else {
790 LogicalAst::Clause(asts.into_iter().map(|ast| (Occur::Should, ast)).collect())
791 };
792 (Some(result_ast), errors)
793 }
794 UserInputLeaf::All => (
795 Some(LogicalAst::Leaf(Box::new(LogicalLiteral::All))),
796 Vec::new(),
797 ),
798 UserInputLeaf::Range {
799 field: full_field_opt,
800 lower,
801 upper,
802 } => {
803 let Some(full_path) = full_field_opt else {
804 return (
805 None,
806 vec![QueryParserError::UnsupportedQuery(
807 "Range query need to target a specific field.".to_string(),
808 )],
809 );
810 };
811 let (field, json_path) = try_tuple!(self
812 .split_full_path(&full_path)
813 .ok_or_else(|| QueryParserError::FieldDoesNotExist(full_path.clone())));
814 let mut errors = Vec::new();
815 let lower = match self.resolve_bound(field, json_path, &lower) {
816 Ok(bound) => bound,
817 Err(error) => {
818 errors.push(error);
819 Bound::Unbounded
820 }
821 };
822 let upper = match self.resolve_bound(field, json_path, &upper) {
823 Ok(bound) => bound,
824 Err(error) => {
825 errors.push(error);
826 Bound::Unbounded
827 }
828 };
829 if lower == Bound::Unbounded && upper == Bound::Unbounded {
830 return (None, errors);
833 }
834 let logical_ast =
835 LogicalAst::Leaf(Box::new(LogicalLiteral::Range { lower, upper }));
836 (Some(logical_ast), errors)
837 }
838 UserInputLeaf::Set {
839 field: full_field_opt,
840 elements,
841 } => {
842 let full_path = try_tuple!(full_field_opt.ok_or_else(|| {
843 QueryParserError::UnsupportedQuery(
844 "Range query need to target a specific field.".to_string(),
845 )
846 }));
847 let (field, json_path) = try_tuple!(self
848 .split_full_path(&full_path)
849 .ok_or_else(|| QueryParserError::FieldDoesNotExist(full_path.clone())));
850 let (elements, errors) = elements
851 .into_iter()
852 .map(|element| self.compute_boundary_term(field, json_path, &element))
853 .partition_result();
854 let logical_ast = LogicalAst::Leaf(Box::new(LogicalLiteral::Set { elements }));
855 (Some(logical_ast), errors)
856 }
857 UserInputLeaf::Exists { .. } => (
858 None,
859 vec![QueryParserError::UnsupportedQuery(
860 "Range query need to target a specific field.".to_string(),
861 )],
862 ),
863 }
864 }
865}
866
867fn convert_literal_to_query(
868 fuzzy: &FxHashMap<Field, Fuzzy>,
869 logical_literal: LogicalLiteral,
870) -> Box<dyn Query> {
871 match logical_literal {
872 LogicalLiteral::Term(term) => {
873 if let Some(fuzzy) = fuzzy.get(&term.field()) {
874 if fuzzy.prefix {
875 Box::new(FuzzyTermQuery::new_prefix(
876 term,
877 fuzzy.distance,
878 fuzzy.transpose_cost_one,
879 ))
880 } else {
881 Box::new(FuzzyTermQuery::new(
882 term,
883 fuzzy.distance,
884 fuzzy.transpose_cost_one,
885 ))
886 }
887 } else {
888 Box::new(TermQuery::new(term, IndexRecordOption::WithFreqs))
889 }
890 }
891 LogicalLiteral::Phrase {
892 terms,
893 slop,
894 prefix,
895 } => {
896 if prefix {
897 Box::new(PhrasePrefixQuery::new_with_offset(terms))
898 } else {
899 Box::new(PhraseQuery::new_with_offset_and_slop(terms, slop))
900 }
901 }
902 LogicalLiteral::Range { lower, upper } => Box::new(RangeQuery::new(lower, upper)),
903 LogicalLiteral::Set { elements, .. } => Box::new(TermSetQuery::new(elements)),
904 LogicalLiteral::All => Box::new(AllQuery),
905 }
906}
907
908fn generate_literals_for_str(
909 field_name: &str,
910 field: Field,
911 phrase: &str,
912 slop: u32,
913 prefix: bool,
914 indexing_options: &TextFieldIndexing,
915 text_analyzer: &mut TextAnalyzer,
916) -> Result<Option<LogicalLiteral>, QueryParserError> {
917 let mut terms: Vec<(usize, Term)> = Vec::new();
918 let mut token_stream = text_analyzer.token_stream(phrase);
919 token_stream.process(&mut |token| {
920 let term = Term::from_field_text(field, &token.text);
921 terms.push((token.position, term));
922 });
923 if terms.len() <= 1 {
924 if prefix {
925 return Err(QueryParserError::PhrasePrefixRequiresAtLeastTwoTerms {
926 phrase: phrase.to_owned(),
927 tokenizer: indexing_options.tokenizer().to_owned(),
928 });
929 }
930 let term_literal_opt = terms
931 .into_iter()
932 .next()
933 .map(|(_, term)| LogicalLiteral::Term(term));
934 return Ok(term_literal_opt);
935 }
936 if !indexing_options.index_option().has_positions() {
937 return Err(QueryParserError::FieldDoesNotHavePositionsIndexed(
938 field_name.to_string(),
939 ));
940 }
941 Ok(Some(LogicalLiteral::Phrase {
942 terms,
943 slop,
944 prefix,
945 }))
946}
947
948fn generate_literals_for_json_object(
949 field_name: &str,
950 field: Field,
951 json_path: &str,
952 phrase: &str,
953 tokenizer_manager: &TokenizerManager,
954 json_options: &JsonObjectOptions,
955) -> Result<Vec<LogicalLiteral>, QueryParserError> {
956 let text_options = json_options.get_text_indexing_options().ok_or_else(|| {
957 QueryParserError::FieldNotIndexed(field_name.to_string())
959 })?;
960 let mut text_analyzer = tokenizer_manager
961 .get(text_options.tokenizer())
962 .ok_or_else(|| QueryParserError::UnknownTokenizer {
963 field: field_name.to_string(),
964 tokenizer: text_options.tokenizer().to_string(),
965 })?;
966 let index_record_option = text_options.index_option();
967 let mut logical_literals = Vec::new();
968
969 let get_term_with_path =
970 || Term::from_field_json_path(field, json_path, json_options.is_expand_dots_enabled());
971
972 if let Some(term) =
974 convert_to_fast_value_and_append_to_json_term(get_term_with_path(), phrase, true)
975 {
976 logical_literals.push(LogicalLiteral::Term(term));
977 }
978
979 let mut positions_and_terms = Vec::<(usize, Term)>::new();
981 let mut token_stream = text_analyzer.token_stream(phrase);
982 token_stream.process(&mut |token| {
983 let mut term = get_term_with_path();
984 term.append_type_and_str(&token.text);
985 positions_and_terms.push((token.position, term.clone()));
986 });
987
988 if positions_and_terms.len() <= 1 {
989 for (_, term) in positions_and_terms {
990 logical_literals.push(LogicalLiteral::Term(term));
991 }
992 return Ok(logical_literals);
993 }
994 if !index_record_option.has_positions() {
995 return Err(QueryParserError::FieldDoesNotHavePositionsIndexed(
996 field_name.to_string(),
997 ));
998 }
999 logical_literals.push(LogicalLiteral::Phrase {
1000 terms: positions_and_terms,
1001 slop: 0,
1002 prefix: false,
1003 });
1004 Ok(logical_literals)
1005}
1006
1007fn convert_to_query(fuzzy: &FxHashMap<Field, Fuzzy>, logical_ast: LogicalAst) -> Box<dyn Query> {
1008 match trim_ast(logical_ast) {
1009 Some(LogicalAst::Clause(trimmed_clause)) => {
1010 let occur_subqueries = trimmed_clause
1011 .into_iter()
1012 .map(|(occur, subquery)| (occur, convert_to_query(fuzzy, subquery)))
1013 .collect::<Vec<_>>();
1014 assert!(
1015 !occur_subqueries.is_empty(),
1016 "Should not be empty after trimming"
1017 );
1018 Box::new(BooleanQuery::new(occur_subqueries))
1019 }
1020 Some(LogicalAst::Leaf(trimmed_logical_literal)) => {
1021 convert_literal_to_query(fuzzy, *trimmed_logical_literal)
1022 }
1023 Some(LogicalAst::Boost(ast, boost)) => {
1024 let query = convert_to_query(fuzzy, *ast);
1025 let boosted_query = BoostQuery::new(query, boost);
1026 Box::new(boosted_query)
1027 }
1028 None => Box::new(EmptyQuery),
1029 }
1030}
1031
1032#[cfg(test)]
1033mod test {
1034 use matches::assert_matches;
1035
1036 use super::super::logical_ast::*;
1037 use super::{QueryParser, QueryParserError};
1038 use crate::query::Query;
1039 use crate::schema::{
1040 FacetOptions, Field, IndexRecordOption, Schema, Term, TextFieldIndexing, TextOptions, FAST,
1041 INDEXED, STORED, STRING, TEXT,
1042 };
1043 use crate::tokenizer::{
1044 LowerCaser, SimpleTokenizer, StopWordFilter, TextAnalyzer, TokenizerManager,
1045 };
1046 use crate::Index;
1047
1048 fn make_schema() -> Schema {
1049 let mut schema_builder = Schema::builder();
1050 let text_field_indexing = TextFieldIndexing::default()
1051 .set_tokenizer("en_with_stop_words")
1052 .set_index_option(IndexRecordOption::WithFreqsAndPositions);
1053 let text_options = TextOptions::default()
1054 .set_indexing_options(text_field_indexing)
1055 .set_stored();
1056 schema_builder.add_text_field("title", TEXT);
1057 schema_builder.add_text_field("text", TEXT);
1058 schema_builder.add_i64_field("signed", INDEXED);
1059 schema_builder.add_u64_field("unsigned", INDEXED);
1060 schema_builder.add_text_field("notindexed_text", STORED);
1061 schema_builder.add_text_field("notindexed_u64", STORED);
1062 schema_builder.add_text_field("notindexed_i64", STORED);
1063 schema_builder.add_text_field("nottokenized", STRING);
1064 schema_builder.add_text_field("with_stop_words", text_options);
1065 schema_builder.add_date_field("date", INDEXED);
1066 schema_builder.add_f64_field("float", INDEXED);
1067 schema_builder.add_facet_field("facet", FacetOptions::default());
1068 schema_builder.add_bytes_field("bytes", INDEXED);
1069 schema_builder.add_bytes_field("bytes_not_indexed", STORED);
1070 schema_builder.add_json_field("json", TEXT);
1071 schema_builder.add_json_field("json_not_indexed", STORED);
1072 schema_builder.add_bool_field("bool", INDEXED);
1073 schema_builder.add_bool_field("notindexed_bool", STORED);
1074 schema_builder.add_u64_field("u64_ff", FAST);
1075 schema_builder.build()
1076 }
1077
1078 fn make_query_parser_with_default_fields(default_fields: &[&'static str]) -> QueryParser {
1079 let schema = make_schema();
1080 let default_fields: Vec<Field> = default_fields
1081 .iter()
1082 .flat_map(|field_name| schema.get_field(field_name))
1083 .collect();
1084 let tokenizer_manager = TokenizerManager::default();
1085 tokenizer_manager.register(
1086 "en_with_stop_words",
1087 TextAnalyzer::builder(SimpleTokenizer::default())
1088 .filter(LowerCaser)
1089 .filter(StopWordFilter::remove(vec!["the".to_string()]))
1090 .build(),
1091 );
1092 QueryParser::new(schema, default_fields, tokenizer_manager)
1093 }
1094
1095 fn make_query_parser() -> QueryParser {
1096 make_query_parser_with_default_fields(&["title", "text"])
1097 }
1098
1099 fn parse_query_to_logical_ast_with_default_fields(
1100 query: &str,
1101 default_conjunction: bool,
1102 default_fields: &[&'static str],
1103 ) -> Result<LogicalAst, QueryParserError> {
1104 let mut query_parser = make_query_parser_with_default_fields(default_fields);
1105 if default_conjunction {
1106 query_parser.set_conjunction_by_default();
1107 }
1108 query_parser.parse_query_to_logical_ast(query)
1109 }
1110
1111 fn parse_query_to_logical_ast(
1112 query: &str,
1113 default_conjunction: bool,
1114 ) -> Result<LogicalAst, QueryParserError> {
1115 parse_query_to_logical_ast_with_default_fields(
1116 query,
1117 default_conjunction,
1118 &["title", "text"],
1119 )
1120 }
1121
1122 #[track_caller]
1123 fn test_parse_query_to_logical_ast_helper_with_default_fields(
1124 query: &str,
1125 expected: &str,
1126 default_conjunction: bool,
1127 default_fields: &[&'static str],
1128 ) {
1129 let query = parse_query_to_logical_ast_with_default_fields(
1130 query,
1131 default_conjunction,
1132 default_fields,
1133 )
1134 .unwrap();
1135 let query_str = format!("{query:?}");
1136 assert_eq!(query_str, expected);
1137 }
1138
1139 #[track_caller]
1140 fn test_parse_query_to_logical_ast_helper(
1141 query: &str,
1142 expected: &str,
1143 default_conjunction: bool,
1144 ) {
1145 test_parse_query_to_logical_ast_helper_with_default_fields(
1146 query,
1147 expected,
1148 default_conjunction,
1149 &["title", "text"],
1150 )
1151 }
1152
1153 #[test]
1154 pub fn test_parse_query_facet() {
1155 let query_parser = make_query_parser();
1156 let query = query_parser.parse_query("facet:/root/branch/leaf").unwrap();
1157 assert_eq!(
1158 format!("{query:?}"),
1159 r#"TermQuery(Term(field=11, type=Facet, Facet(/root/branch/leaf)))"#
1160 );
1161 }
1162
1163 #[test]
1164 pub fn test_parse_query_with_boost() {
1165 let mut query_parser = make_query_parser();
1166 let schema = make_schema();
1167 let text_field = schema.get_field("text").unwrap();
1168 query_parser.set_field_boost(text_field, 2.0);
1169 let query = query_parser.parse_query("text:hello").unwrap();
1170 assert_eq!(
1171 format!("{query:?}"),
1172 r#"Boost(query=TermQuery(Term(field=1, type=Str, "hello")), boost=2)"#
1173 );
1174 }
1175
1176 #[test]
1177 pub fn test_parse_query_range_with_boost() {
1178 let query = make_query_parser().parse_query("title:[A TO B]").unwrap();
1179 assert_eq!(
1180 format!("{query:?}"),
1181 "RangeQuery { bounds: BoundsRange { lower_bound: Included(Term(field=0, type=Str, \
1182 \"a\")), upper_bound: Included(Term(field=0, type=Str, \"b\")) } }"
1183 );
1184 }
1185
1186 #[test]
1187 pub fn test_parse_query_with_default_boost_and_custom_boost() {
1188 let mut query_parser = make_query_parser();
1189 let schema = make_schema();
1190 let text_field = schema.get_field("text").unwrap();
1191 query_parser.set_field_boost(text_field, 2.0);
1192 let query = query_parser.parse_query("text:hello^2").unwrap();
1193 assert_eq!(
1194 format!("{query:?}"),
1195 r#"Boost(query=Boost(query=TermQuery(Term(field=1, type=Str, "hello")), boost=2), boost=2)"#
1196 );
1197 }
1198
1199 #[test]
1200 pub fn test_parse_nonindexed_field_yields_error() {
1201 let query_parser = make_query_parser();
1202
1203 let is_not_indexed_err = |query: &str| {
1204 let result: Result<Box<dyn Query>, QueryParserError> = query_parser.parse_query(query);
1205 if let Err(QueryParserError::FieldNotIndexed(field_name)) = result {
1206 Some(field_name)
1207 } else {
1208 None
1209 }
1210 };
1211
1212 assert_eq!(
1213 is_not_indexed_err("notindexed_text:titi"),
1214 Some(String::from("notindexed_text"))
1215 );
1216 assert_eq!(
1217 is_not_indexed_err("notindexed_u64:23424"),
1218 Some(String::from("notindexed_u64"))
1219 );
1220 assert_eq!(
1221 is_not_indexed_err("notindexed_i64:-234324"),
1222 Some(String::from("notindexed_i64"))
1223 );
1224 assert_eq!(
1225 is_not_indexed_err("notindexed_bool:true"),
1226 Some(String::from("notindexed_bool"))
1227 );
1228 }
1229
1230 #[test]
1231 pub fn test_parse_query_untokenized() {
1232 test_parse_query_to_logical_ast_helper(
1233 "nottokenized:\"wordone wordtwo\"",
1234 r#"Term(field=7, type=Str, "wordone wordtwo")"#,
1235 false,
1236 );
1237 }
1238
1239 #[test]
1240 pub fn test_parse_query_empty() {
1241 test_parse_query_to_logical_ast_helper("", "<emptyclause>", false);
1242 test_parse_query_to_logical_ast_helper(" ", "<emptyclause>", false);
1243 let query_parser = make_query_parser();
1244 let query_result = query_parser.parse_query("");
1245 let query = query_result.unwrap();
1246 assert_eq!(format!("{query:?}"), "EmptyQuery");
1247 }
1248
1249 #[test]
1250 pub fn test_parse_query_ints() {
1251 let query_parser = make_query_parser();
1252 assert!(query_parser.parse_query("signed:2324").is_ok());
1253 assert!(query_parser.parse_query("signed:\"22\"").is_ok());
1254 assert!(query_parser.parse_query("signed:\"-2234\"").is_ok());
1255 assert!(query_parser
1256 .parse_query("signed:\"-9999999999999\"")
1257 .is_ok());
1258 assert!(query_parser.parse_query("signed:\"a\"").is_err());
1259 assert!(query_parser.parse_query("signed:\"2a\"").is_err());
1260 assert!(query_parser
1261 .parse_query("signed:\"18446744073709551615\"")
1262 .is_err());
1263 assert!(query_parser.parse_query("unsigned:\"2\"").is_ok());
1264 assert!(query_parser.parse_query("unsigned:\"-2\"").is_err());
1265 assert!(query_parser
1266 .parse_query("unsigned:\"18446744073709551615\"")
1267 .is_ok());
1268 assert!(query_parser.parse_query("float:\"3.1\"").is_ok());
1269 assert!(query_parser.parse_query("float:\"-2.4\"").is_ok());
1270 assert!(query_parser.parse_query("float:\"2.1.2\"").is_err());
1271 assert!(query_parser.parse_query("float:\"2.1a\"").is_err());
1272 assert!(query_parser
1273 .parse_query("float:\"18446744073709551615.0\"")
1274 .is_ok());
1275 test_parse_query_to_logical_ast_helper(
1276 "unsigned:2324",
1277 "Term(field=3, type=U64, 2324)",
1278 false,
1279 );
1280
1281 test_parse_query_to_logical_ast_helper(
1282 "signed:-2324",
1283 &format!(
1284 "{:?}",
1285 Term::from_field_i64(Field::from_field_id(2u32), -2324)
1286 ),
1287 false,
1288 );
1289
1290 test_parse_query_to_logical_ast_helper(
1291 "float:2.5",
1292 &format!(
1293 "{:?}",
1294 Term::from_field_f64(Field::from_field_id(10u32), 2.5)
1295 ),
1296 false,
1297 );
1298 }
1299
1300 #[test]
1301 fn test_parse_bytes() {
1302 test_parse_query_to_logical_ast_helper(
1303 "bytes:YnVidQ==",
1304 "Term(field=12, type=Bytes, [98, 117, 98, 117])",
1305 false,
1306 );
1307 }
1308
1309 #[test]
1310 fn test_parse_bool() {
1311 test_parse_query_to_logical_ast_helper(
1312 "bool:true",
1313 &format!(
1314 "{:?}",
1315 Term::from_field_bool(Field::from_field_id(16u32), true),
1316 ),
1317 false,
1318 );
1319 }
1320
1321 #[test]
1322 fn test_parse_bytes_not_indexed() {
1323 let error = parse_query_to_logical_ast("bytes_not_indexed:aaa", false).unwrap_err();
1324 assert!(matches!(error, QueryParserError::FieldNotIndexed(_)));
1325 }
1326
1327 #[test]
1328 fn test_json_field() {
1329 test_parse_query_to_logical_ast_helper(
1330 "json.titi:hello",
1331 "Term(field=14, type=Json, path=titi, type=Str, \"hello\")",
1332 false,
1333 );
1334 }
1335
1336 fn extract_query_term_json_path(query: &str) -> String {
1337 let LogicalAst::Leaf(literal) = parse_query_to_logical_ast(query, false).unwrap() else {
1338 panic!();
1339 };
1340 let LogicalLiteral::Term(term) = *literal else {
1341 panic!();
1342 };
1343 std::str::from_utf8(term.serialized_value_bytes())
1344 .unwrap()
1345 .to_string()
1346 }
1347
1348 #[test]
1349 fn test_json_field_query_with_escaped_dot() {
1350 assert_eq!(
1351 extract_query_term_json_path(r#"json.k8s.node.name:hello"#),
1352 "k8s\u{1}node\u{1}name\0shello"
1353 );
1354 assert_eq!(
1355 extract_query_term_json_path(r"json.k8s\.node\.name:hello"),
1356 "k8s.node.name\0shello"
1357 );
1358 }
1359
1360 #[test]
1361 fn test_json_field_possibly_a_number() {
1362 test_parse_query_to_logical_ast_helper(
1363 "json.titi:5",
1364 r#"(Term(field=14, type=Json, path=titi, type=I64, 5) Term(field=14, type=Json, path=titi, type=Str, "5"))"#,
1365 true,
1366 );
1367 test_parse_query_to_logical_ast_helper(
1368 "json.titi:-5",
1369 r#"(Term(field=14, type=Json, path=titi, type=I64, -5) Term(field=14, type=Json, path=titi, type=Str, "5"))"#, true,
1371 );
1372 test_parse_query_to_logical_ast_helper(
1373 "json.titi:10000000000000000000",
1374 r#"(Term(field=14, type=Json, path=titi, type=U64, 10000000000000000000) Term(field=14, type=Json, path=titi, type=Str, "10000000000000000000"))"#,
1375 true,
1376 );
1377 test_parse_query_to_logical_ast_helper(
1378 "json.titi:-5.2",
1379 r#"(Term(field=14, type=Json, path=titi, type=F64, -5.2) "[(0, Term(field=14, type=Json, path=titi, type=Str, "5")), (1, Term(field=14, type=Json, path=titi, type=Str, "2"))]")"#,
1380 true,
1381 );
1382 }
1383
1384 #[test]
1385 fn test_json_field_possibly_a_date() {
1386 test_parse_query_to_logical_ast_helper(
1387 r#"json.date:"2019-10-12T07:20:50.52Z""#,
1388 r#"(Term(field=14, type=Json, path=date, type=Date, 2019-10-12T07:20:50Z) "[(0, Term(field=14, type=Json, path=date, type=Str, "2019")), (1, Term(field=14, type=Json, path=date, type=Str, "10")), (2, Term(field=14, type=Json, path=date, type=Str, "12t07")), (3, Term(field=14, type=Json, path=date, type=Str, "20")), (4, Term(field=14, type=Json, path=date, type=Str, "50")), (5, Term(field=14, type=Json, path=date, type=Str, "52z"))]")"#,
1389 true,
1390 );
1391 }
1392
1393 #[test]
1394 fn test_json_field_possibly_a_bool() {
1395 test_parse_query_to_logical_ast_helper(
1396 "json.titi:true",
1397 r#"(Term(field=14, type=Json, path=titi, type=Bool, true) Term(field=14, type=Json, path=titi, type=Str, "true"))"#,
1398 true,
1399 );
1400 }
1401
1402 #[test]
1403 fn test_json_field_not_indexed() {
1404 let error = parse_query_to_logical_ast("json_not_indexed.titi:hello", false).unwrap_err();
1405 assert!(matches!(error, QueryParserError::FieldNotIndexed(_)));
1406 }
1407
1408 fn test_query_to_logical_ast_with_default_json(
1409 query: &str,
1410 expected: &str,
1411 default_conjunction: bool,
1412 ) {
1413 let mut query_parser = make_query_parser_with_default_fields(&["json"]);
1414 if default_conjunction {
1415 query_parser.set_conjunction_by_default();
1416 }
1417 let ast = query_parser.parse_query_to_logical_ast(query).unwrap();
1418 let ast_str = format!("{ast:?}");
1419 assert_eq!(ast_str, expected);
1420 }
1421
1422 #[test]
1423 fn test_json_default() {
1424 test_query_to_logical_ast_with_default_json(
1425 "titi:4",
1426 "(Term(field=14, type=Json, path=titi, type=I64, 4) Term(field=14, type=Json, \
1427 path=titi, type=Str, \"4\"))",
1428 false,
1429 );
1430 }
1431
1432 #[test]
1433 fn test_json_default_with_different_field() {
1434 for conjunction in [false, true] {
1435 test_query_to_logical_ast_with_default_json(
1436 "text:4",
1437 r#"Term(field=1, type=Str, "4")"#,
1438 conjunction,
1439 );
1440 }
1441 }
1442
1443 #[test]
1444 fn test_json_default_with_same_field() {
1445 for conjunction in [false, true] {
1446 test_query_to_logical_ast_with_default_json(
1447 "json:4",
1448 r#"(Term(field=14, type=Json, path=, type=I64, 4) Term(field=14, type=Json, path=, type=Str, "4"))"#,
1449 conjunction,
1450 );
1451 }
1452 }
1453
1454 #[test]
1455 fn test_parse_bytes_phrase() {
1456 test_parse_query_to_logical_ast_helper(
1457 "bytes:\"YnVidQ==\"",
1458 "Term(field=12, type=Bytes, [98, 117, 98, 117])",
1459 false,
1460 );
1461 }
1462
1463 #[test]
1464 fn test_parse_bytes_invalid_base64() {
1465 let base64_err: QueryParserError =
1466 parse_query_to_logical_ast("bytes:aa", false).unwrap_err();
1467 assert!(matches!(base64_err, QueryParserError::ExpectedBase64(_)));
1468 }
1469
1470 #[test]
1471 fn test_parse_query_to_ast_ab_c() {
1472 test_parse_query_to_logical_ast_helper(
1473 "(+title:a +title:b) title:c",
1474 r#"((+Term(field=0, type=Str, "a") +Term(field=0, type=Str, "b")) Term(field=0, type=Str, "c"))"#,
1475 false,
1476 );
1477 test_parse_query_to_logical_ast_helper(
1478 "(+title:a +title:b) title:c",
1479 r#"(+Term(field=0, type=Str, "a") +Term(field=0, type=Str, "b") +Term(field=0, type=Str, "c"))"#,
1480 true,
1481 );
1482 }
1483
1484 #[test]
1485 pub fn test_parse_query_to_ast_single_term() {
1486 test_parse_query_to_logical_ast_helper(
1487 "title:toto",
1488 r#"Term(field=0, type=Str, "toto")"#,
1489 false,
1490 );
1491 test_parse_query_to_logical_ast_helper(
1492 "+title:toto",
1493 r#"Term(field=0, type=Str, "toto")"#,
1494 false,
1495 );
1496 test_parse_query_to_logical_ast_helper(
1497 "+title:toto -titi",
1498 r#"(+Term(field=0, type=Str, "toto") -(Term(field=0, type=Str, "titi") Term(field=1, type=Str, "titi")))"#,
1499 false,
1500 );
1501 }
1502
1503 #[test]
1504 fn test_single_negative_term() {
1505 assert_matches!(
1506 parse_query_to_logical_ast("-title:toto", false),
1507 Err(QueryParserError::AllButQueryForbidden)
1508 );
1509 }
1510
1511 #[test]
1512 pub fn test_parse_query_to_ast_two_terms() {
1513 test_parse_query_to_logical_ast_helper(
1514 "title:a b",
1515 r#"(Term(field=0, type=Str, "a") Term(field=0, type=Str, "b") Term(field=1, type=Str, "b"))"#,
1516 false,
1517 );
1518 test_parse_query_to_logical_ast_helper(
1519 r#"title:"a b""#,
1520 r#""[(0, Term(field=0, type=Str, "a")), (1, Term(field=0, type=Str, "b"))]""#,
1521 false,
1522 );
1523 }
1524 #[test]
1525 pub fn test_parse_query_all_query() {
1526 let logical_ast = parse_query_to_logical_ast("*", false).unwrap();
1527 assert_eq!(format!("{logical_ast:?}"), "*");
1528 }
1529
1530 #[test]
1531 pub fn test_parse_query_range_require_a_target_field() {
1532 let query_parser_error = parse_query_to_logical_ast("[A TO B]", false).err().unwrap();
1533 assert_eq!(
1534 query_parser_error.to_string(),
1535 "Unsupported query: Range query need to target a specific field."
1536 );
1537 }
1538
1539 #[test]
1540 pub fn test_parse_query_to_ast_ranges() {
1541 test_parse_query_to_logical_ast_helper(
1542 "title:[a TO b]",
1543 r#"(Included(Term(field=0, type=Str, "a")) TO Included(Term(field=0, type=Str, "b")))"#,
1544 false,
1545 );
1546 test_parse_query_to_logical_ast_helper(
1547 "title:{titi TO toto}",
1548 r#"(Excluded(Term(field=0, type=Str, "titi")) TO Excluded(Term(field=0, type=Str, "toto")))"#,
1549 false,
1550 );
1551 test_parse_query_to_logical_ast_helper(
1552 "title:{* TO toto}",
1553 r#"(Unbounded TO Excluded(Term(field=0, type=Str, "toto")))"#,
1554 false,
1555 );
1556 test_parse_query_to_logical_ast_helper(
1557 "title:{titi TO *}",
1558 r#"(Excluded(Term(field=0, type=Str, "titi")) TO Unbounded)"#,
1559 false,
1560 );
1561 test_parse_query_to_logical_ast_helper(
1562 "signed:{-5 TO 3}",
1563 r#"(Excluded(Term(field=2, type=I64, -5)) TO Excluded(Term(field=2, type=I64, 3)))"#,
1564 false,
1565 );
1566 test_parse_query_to_logical_ast_helper(
1567 "float:{-1.5 TO 1.5}",
1568 r#"(Excluded(Term(field=10, type=F64, -1.5)) TO Excluded(Term(field=10, type=F64, 1.5)))"#,
1569 false,
1570 );
1571 test_parse_query_to_logical_ast_helper(
1572 "u64_ff:[7 TO 77]",
1573 r#"(Included(Term(field=18, type=U64, 7)) TO Included(Term(field=18, type=U64, 77)))"#,
1574 false,
1575 );
1576 }
1577
1578 #[test]
1579 pub fn test_query_parser_field_does_not_exist() {
1580 let query_parser = make_query_parser();
1581 assert_eq!(
1582 query_parser
1583 .parse_query("boujou:\"18446744073709551615\"")
1584 .unwrap_err(),
1585 QueryParserError::FieldDoesNotExist("boujou".to_string())
1586 );
1587 }
1588
1589 #[test]
1590 pub fn test_query_parser_field_not_indexed() {
1591 let query_parser = make_query_parser();
1592 assert_matches!(
1593 query_parser.parse_query("notindexed_text:\"18446744073709551615\""),
1594 Err(QueryParserError::FieldNotIndexed(_))
1595 );
1596 }
1597
1598 #[test]
1599 pub fn test_unknown_tokenizer() {
1600 let mut schema_builder = Schema::builder();
1601 let text_field_indexing = TextFieldIndexing::default()
1602 .set_tokenizer("nonexistingtokenizer")
1603 .set_index_option(IndexRecordOption::Basic);
1604 let text_options = TextOptions::default().set_indexing_options(text_field_indexing);
1605 let title = schema_builder.add_text_field("title", text_options);
1606 let schema = schema_builder.build();
1607 let default_fields = vec![title];
1608 let tokenizer_manager = TokenizerManager::default();
1609 let query_parser = QueryParser::new(schema, default_fields, tokenizer_manager);
1610
1611 assert_matches!(
1612 query_parser.parse_query("title:\"happy tax payer\""),
1613 Err(QueryParserError::UnknownTokenizer { .. })
1614 );
1615 }
1616
1617 #[test]
1618 pub fn test_query_parser_no_positions() {
1619 let mut schema_builder = Schema::builder();
1620 let text_field_indexing = TextFieldIndexing::default()
1621 .set_tokenizer("customtokenizer")
1622 .set_index_option(IndexRecordOption::Basic);
1623 let text_options = TextOptions::default().set_indexing_options(text_field_indexing);
1624 let title = schema_builder.add_text_field("title", text_options);
1625 let schema = schema_builder.build();
1626 let index = Index::create_in_ram(schema);
1627 index
1628 .tokenizers()
1629 .register("customtokenizer", SimpleTokenizer::default());
1630 let query_parser = QueryParser::for_index(&index, vec![title]);
1631 assert_eq!(
1632 query_parser.parse_query("title:\"happy tax\"").unwrap_err(),
1633 QueryParserError::FieldDoesNotHavePositionsIndexed("title".to_string())
1634 );
1635 }
1636
1637 #[test]
1638 pub fn test_query_parser_expected_int() {
1639 let query_parser = make_query_parser();
1640 assert_matches!(
1641 query_parser.parse_query("unsigned:18a"),
1642 Err(QueryParserError::ExpectedInt(_))
1643 );
1644 assert!(query_parser.parse_query("unsigned:\"18\"").is_ok());
1645 assert_matches!(
1646 query_parser.parse_query("signed:18b"),
1647 Err(QueryParserError::ExpectedInt(_))
1648 );
1649 assert!(query_parser.parse_query("float:\"1.8\"").is_ok());
1650 assert_matches!(
1651 query_parser.parse_query("float:1.8a"),
1652 Err(QueryParserError::ExpectedFloat(_))
1653 );
1654 }
1655
1656 #[test]
1657 pub fn test_query_parser_expected_bool() {
1658 let query_parser = make_query_parser();
1659 assert_matches!(
1660 query_parser.parse_query("bool:brie"),
1661 Err(QueryParserError::ExpectedBool(_))
1662 );
1663 assert!(query_parser.parse_query("bool:\"true\"").is_ok());
1664 assert!(query_parser.parse_query("bool:\"false\"").is_ok());
1665 }
1666
1667 #[test]
1668 pub fn test_query_parser_expected_date() {
1669 let query_parser = make_query_parser();
1670 assert_matches!(
1671 query_parser.parse_query("date:18a"),
1672 Err(QueryParserError::DateFormatError(_))
1673 );
1674 test_parse_query_to_logical_ast_helper(
1675 r#"date:"2010-11-21T09:55:06.000000000+02:00""#,
1676 r#"Term(field=9, type=Date, 2010-11-21T07:55:06Z)"#,
1677 true,
1678 );
1679 test_parse_query_to_logical_ast_helper(
1680 r#"date:"1985-04-12T23:20:50.52Z""#,
1681 r#"Term(field=9, type=Date, 1985-04-12T23:20:50Z)"#,
1682 true,
1683 );
1684 }
1685
1686 #[test]
1687 pub fn test_query_parser_expected_facet() {
1688 let query_parser = make_query_parser();
1689 match query_parser.parse_query("facet:INVALID") {
1690 Ok(_) => panic!("should never succeed"),
1691 Err(e) => assert_eq!(
1692 "The facet field is malformed: Failed to parse the facet string: 'INVALID'",
1693 format!("{e}")
1694 ),
1695 }
1696 assert!(query_parser.parse_query("facet:\"/foo/bar\"").is_ok());
1697 }
1698
1699 #[test]
1700 pub fn test_query_parser_not_empty_but_no_tokens() {
1701 let query_parser = make_query_parser();
1702 assert!(query_parser.parse_query(" !, ").is_ok());
1703 assert!(query_parser.parse_query("with_stop_words:the").is_ok());
1704 }
1705
1706 #[test]
1707 pub fn test_parse_query_single_negative_term_through_error() {
1708 assert_matches!(
1709 parse_query_to_logical_ast("-title:toto", true),
1710 Err(QueryParserError::AllButQueryForbidden)
1711 );
1712 assert_matches!(
1713 parse_query_to_logical_ast("-title:toto", false),
1714 Err(QueryParserError::AllButQueryForbidden)
1715 );
1716 }
1717
1718 #[test]
1719 pub fn test_parse_query_to_ast_conjunction() {
1720 test_parse_query_to_logical_ast_helper(
1721 "title:toto",
1722 r#"Term(field=0, type=Str, "toto")"#,
1723 true,
1724 );
1725 test_parse_query_to_logical_ast_helper(
1726 "+title:toto",
1727 r#"Term(field=0, type=Str, "toto")"#,
1728 true,
1729 );
1730 test_parse_query_to_logical_ast_helper(
1731 "+title:toto -titi",
1732 r#"(+Term(field=0, type=Str, "toto") -(Term(field=0, type=Str, "titi") Term(field=1, type=Str, "titi")))"#,
1733 true,
1734 );
1735 test_parse_query_to_logical_ast_helper(
1736 "title:a b",
1737 r#"(+Term(field=0, type=Str, "a") +(Term(field=0, type=Str, "b") Term(field=1, type=Str, "b")))"#,
1738 true,
1739 );
1740 test_parse_query_to_logical_ast_helper(
1741 "title:\"a b\"",
1742 r#""[(0, Term(field=0, type=Str, "a")), (1, Term(field=0, type=Str, "b"))]""#,
1743 true,
1744 );
1745 }
1746
1747 #[test]
1748 pub fn test_parse_query_negative() {
1749 test_parse_query_to_logical_ast_helper(
1750 "title:b -title:a",
1751 r#"(+Term(field=0, type=Str, "b") -Term(field=0, type=Str, "a"))"#,
1752 true,
1753 );
1754
1755 test_parse_query_to_logical_ast_helper(
1756 "title:b -(-title:a -title:c)",
1757 r#"(+Term(field=0, type=Str, "b") -(-Term(field=0, type=Str, "a") -Term(field=0, type=Str, "c")))"#,
1758 true,
1759 );
1760 }
1761
1762 #[test]
1763 pub fn test_query_parser_hyphen() {
1764 test_parse_query_to_logical_ast_helper(
1765 "title:www-form-encoded",
1766 r#""[(0, Term(field=0, type=Str, "www")), (1, Term(field=0, type=Str, "form")), (2, Term(field=0, type=Str, "encoded"))]""#,
1767 false,
1768 );
1769 }
1770
1771 #[test]
1772 fn test_and_default_regardless_of_default_conjunctive() {
1773 for &default_conjunction in &[false, true] {
1774 test_parse_query_to_logical_ast_helper(
1775 "title:a AND title:b",
1776 r#"(+Term(field=0, type=Str, "a") +Term(field=0, type=Str, "b"))"#,
1777 default_conjunction,
1778 );
1779 }
1780 }
1781
1782 #[test]
1783 fn test_or_default_conjunctive() {
1784 for &default_conjunction in &[false, true] {
1785 test_parse_query_to_logical_ast_helper(
1786 "title:a OR title:b",
1787 r#"(Term(field=0, type=Str, "a") Term(field=0, type=Str, "b"))"#,
1788 default_conjunction,
1789 );
1790 }
1791 }
1792
1793 #[test]
1794 fn test_escaped_field() {
1795 let mut schema_builder = Schema::builder();
1796 schema_builder.add_text_field(r"a\.b", STRING);
1797 let schema = schema_builder.build();
1798 let query_parser = QueryParser::new(schema, Vec::new(), TokenizerManager::default());
1799 let query = query_parser.parse_query(r"a\.b:hello").unwrap();
1800 assert_eq!(
1801 format!("{query:?}"),
1802 "TermQuery(Term(field=0, type=Str, \"hello\"))"
1803 );
1804 }
1805
1806 #[test]
1807 fn test_split_full_path() {
1808 let mut schema_builder = Schema::builder();
1809 schema_builder.add_text_field("second", STRING);
1810 schema_builder.add_text_field("first", STRING);
1811 schema_builder.add_text_field("first.toto", STRING);
1812 schema_builder.add_text_field("first.toto.titi", STRING);
1813 schema_builder.add_text_field("third.a.b.c", STRING);
1814 let schema = schema_builder.build();
1815 let query_parser =
1816 QueryParser::new(schema.clone(), Vec::new(), TokenizerManager::default());
1817 assert_eq!(
1818 query_parser.split_full_path("first.toto"),
1819 Some((schema.get_field("first.toto").unwrap(), ""))
1820 );
1821 assert_eq!(
1822 query_parser.split_full_path("first.toto.bubu"),
1823 Some((schema.get_field("first.toto").unwrap(), "bubu"))
1824 );
1825 assert_eq!(
1826 query_parser.split_full_path("first.toto.titi"),
1827 Some((schema.get_field("first.toto.titi").unwrap(), ""))
1828 );
1829 assert_eq!(
1830 query_parser.split_full_path("first.titi"),
1831 Some((schema.get_field("first").unwrap(), "titi"))
1832 );
1833 assert_eq!(query_parser.split_full_path("third"), None);
1834 assert_eq!(query_parser.split_full_path("hello.toto"), None);
1835 assert_eq!(query_parser.split_full_path(""), None);
1836 assert_eq!(query_parser.split_full_path("firsty"), None);
1837 }
1838
1839 #[test]
1840 pub fn test_phrase_slop() {
1841 test_parse_query_to_logical_ast_helper(
1842 "\"a b\"~0",
1843 r#"("[(0, Term(field=0, type=Str, "a")), (1, Term(field=0, type=Str, "b"))]" "[(0, Term(field=1, type=Str, "a")), (1, Term(field=1, type=Str, "b"))]")"#,
1844 false,
1845 );
1846 test_parse_query_to_logical_ast_helper(
1847 "\"a b\"~2",
1848 r#"("[(0, Term(field=0, type=Str, "a")), (1, Term(field=0, type=Str, "b"))]"~2 "[(0, Term(field=1, type=Str, "a")), (1, Term(field=1, type=Str, "b"))]"~2)"#,
1849 false,
1850 );
1851 test_parse_query_to_logical_ast_helper(
1852 "title:\"a b~4\"~2",
1853 r#""[(0, Term(field=0, type=Str, "a")), (1, Term(field=0, type=Str, "b")), (2, Term(field=0, type=Str, "4"))]"~2"#,
1854 false,
1855 );
1856 }
1857
1858 #[test]
1859 pub fn test_phrase_prefix() {
1860 test_parse_query_to_logical_ast_helper(
1861 "\"big bad wo\"*",
1862 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"))]"*)"#,
1863 false,
1864 );
1865
1866 let query_parser = make_query_parser();
1867 let query = query_parser.parse_query("\"big bad wo\"*").unwrap();
1868 assert_eq!(
1869 format!("{query:?}"),
1870 "BooleanQuery { subqueries: [(Should, PhrasePrefixQuery { field: Field(0), \
1871 phrase_terms: [(0, Term(field=0, type=Str, \"big\")), (1, Term(field=0, type=Str, \
1872 \"bad\"))], prefix: (2, Term(field=0, type=Str, \"wo\")), max_expansions: 50 }), \
1873 (Should, PhrasePrefixQuery { field: Field(1), phrase_terms: [(0, Term(field=1, \
1874 type=Str, \"big\")), (1, Term(field=1, type=Str, \"bad\"))], prefix: (2, \
1875 Term(field=1, type=Str, \"wo\")), max_expansions: 50 })], \
1876 minimum_number_should_match: 1 }"
1877 );
1878 }
1879
1880 #[test]
1881 pub fn test_phrase_prefix_too_short() {
1882 let err = parse_query_to_logical_ast("\"wo\"*", true).unwrap_err();
1883 assert_eq!(
1884 err,
1885 QueryParserError::PhrasePrefixRequiresAtLeastTwoTerms {
1886 phrase: "wo".to_owned(),
1887 tokenizer: "default".to_owned()
1888 }
1889 );
1890
1891 let err = parse_query_to_logical_ast("\"\"*", true).unwrap_err();
1892 assert_eq!(
1893 err,
1894 QueryParserError::PhrasePrefixRequiresAtLeastTwoTerms {
1895 phrase: "".to_owned(),
1896 tokenizer: "default".to_owned()
1897 }
1898 );
1899 }
1900
1901 #[test]
1902 pub fn test_term_set_query() {
1903 test_parse_query_to_logical_ast_helper(
1904 "title: IN [a b cd]",
1905 r#"IN [Term(field=0, type=Str, "a"), Term(field=0, type=Str, "b"), Term(field=0, type=Str, "cd")]"#,
1906 false,
1907 );
1908 test_parse_query_to_logical_ast_helper(
1909 "bytes: IN [AA== ABA= ABCD]",
1910 r#"IN [Term(field=12, type=Bytes, [0]), Term(field=12, type=Bytes, [0, 16]), Term(field=12, type=Bytes, [0, 16, 131])]"#,
1911 false,
1912 );
1913 test_parse_query_to_logical_ast_helper(
1914 "signed: IN [1 2 -3]",
1915 r#"IN [Term(field=2, type=I64, 1), Term(field=2, type=I64, 2), Term(field=2, type=I64, -3)]"#,
1916 false,
1917 );
1918
1919 test_parse_query_to_logical_ast_helper(
1920 "float: IN [1.1 2.2 -3.3]",
1921 r#"IN [Term(field=10, type=F64, 1.1), Term(field=10, type=F64, 2.2), Term(field=10, type=F64, -3.3)]"#,
1922 false,
1923 );
1924 }
1925
1926 #[test]
1927 pub fn test_set_field_fuzzy() {
1928 {
1929 let mut query_parser = make_query_parser();
1930 query_parser.set_field_fuzzy(
1931 query_parser.schema.get_field("title").unwrap(),
1932 false,
1933 1,
1934 true,
1935 );
1936 let query = query_parser.parse_query("abc").unwrap();
1937 assert_eq!(
1938 format!("{query:?}"),
1939 "BooleanQuery { subqueries: [(Should, FuzzyTermQuery { term: Term(field=0, \
1940 type=Str, \"abc\"), distance: 1, transposition_cost_one: true, prefix: false }), \
1941 (Should, TermQuery(Term(field=1, type=Str, \"abc\")))], \
1942 minimum_number_should_match: 1 }"
1943 );
1944 }
1945
1946 {
1947 let mut query_parser = make_query_parser();
1948 query_parser.set_field_fuzzy(
1949 query_parser.schema.get_field("text").unwrap(),
1950 true,
1951 2,
1952 false,
1953 );
1954 let query = query_parser.parse_query("abc").unwrap();
1955 assert_eq!(
1956 format!("{query:?}"),
1957 "BooleanQuery { subqueries: [(Should, TermQuery(Term(field=0, type=Str, \
1958 \"abc\"))), (Should, FuzzyTermQuery { term: Term(field=1, type=Str, \"abc\"), \
1959 distance: 2, transposition_cost_one: false, prefix: true })], \
1960 minimum_number_should_match: 1 }"
1961 );
1962 }
1963 }
1964
1965 #[test]
1966 pub fn test_set_default_field_integer() {
1967 test_parse_query_to_logical_ast_helper_with_default_fields(
1968 "2324",
1969 "(Term(field=0, type=Str, \"2324\") Term(field=2, type=I64, 2324))",
1970 false,
1971 &["title", "signed"],
1972 );
1973
1974 test_parse_query_to_logical_ast_helper_with_default_fields(
1975 "abc",
1976 "Term(field=0, type=Str, \"abc\")",
1977 false,
1978 &["title", "signed"],
1979 );
1980
1981 let query_parser = make_query_parser_with_default_fields(&["signed"]);
1982 assert_matches!(
1983 query_parser.parse_query("abc"),
1984 Err(QueryParserError::ExpectedInt(_))
1985 );
1986 }
1987}