diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/match_bool_prefix.rs b/quickwit/quickwit-query/src/elastic_query_dsl/match_bool_prefix.rs index 3d98ebe116a..03997449519 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/match_bool_prefix.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/match_bool_prefix.rs @@ -19,7 +19,7 @@ use serde::Deserialize; -use super::StringOrStructForSerialization; +use super::{ElasticQueryDslInner, StringOrStructForSerialization}; use crate::elastic_query_dsl::match_query::MatchQueryParams; use crate::elastic_query_dsl::{default_max_expansions, ConvertableToQueryAst}; use crate::query_ast::{FullTextParams, FullTextQuery, QueryAst}; @@ -52,6 +52,12 @@ impl ConvertableToQueryAst for MatchBoolPrefixQuery { } } +impl From for ElasticQueryDslInner { + fn from(match_bool_prefix_query: MatchBoolPrefixQuery) -> Self { + ElasticQueryDslInner::MatchBoolPrefix(match_bool_prefix_query) + } +} + impl From>> for MatchBoolPrefixQuery { fn from( match_query_params: OneFieldMap>, diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/match_phrase_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/match_phrase_query.rs index ac6c82723d9..3260cec01ad 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/match_phrase_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/match_phrase_query.rs @@ -19,7 +19,9 @@ use serde::Deserialize; -use crate::elastic_query_dsl::{ConvertableToQueryAst, StringOrStructForSerialization}; +use crate::elastic_query_dsl::{ + ConvertableToQueryAst, ElasticQueryDslInner, StringOrStructForSerialization, +}; use crate::query_ast::{FullTextMode, FullTextParams, FullTextQuery, QueryAst}; use crate::{MatchAllOrNone, OneFieldMap}; @@ -35,13 +37,13 @@ pub(crate) struct MatchPhraseQuery { #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] #[serde(deny_unknown_fields)] pub struct MatchPhraseQueryParams { - query: String, + pub(crate) query: String, #[serde(default)] - zero_terms_query: MatchAllOrNone, + pub(crate) zero_terms_query: MatchAllOrNone, #[serde(default)] - analyzer: Option, + pub(crate) analyzer: Option, #[serde(default)] - slop: u32, + pub(crate) slop: u32, } impl ConvertableToQueryAst for MatchPhraseQuery { @@ -61,6 +63,12 @@ impl ConvertableToQueryAst for MatchPhraseQuery { } } +impl From for ElasticQueryDslInner { + fn from(match_phrase_query: MatchPhraseQuery) -> Self { + ElasticQueryDslInner::MatchPhrase(match_phrase_query) + } +} + impl From>> for MatchPhraseQuery { diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/multi_match.rs b/quickwit/quickwit-query/src/elastic_query_dsl/multi_match.rs index 7e7d485692c..0c316449b2d 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/multi_match.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/multi_match.rs @@ -22,6 +22,7 @@ use serde_with::formats::PreferMany; use serde_with::{serde_as, OneOrMany}; use crate::elastic_query_dsl::bool_query::BoolQuery; +use crate::elastic_query_dsl::match_bool_prefix::MatchBoolPrefixQuery; use crate::elastic_query_dsl::match_phrase_query::{MatchPhraseQuery, MatchPhraseQueryParams}; use crate::elastic_query_dsl::match_query::{MatchQuery, MatchQueryParams}; use crate::elastic_query_dsl::phrase_prefix_query::{ @@ -78,7 +79,15 @@ fn deserialize_match_query_for_one_field( }; Ok(ElasticQueryDslInner::MatchPhrasePrefix(phrase_prefix)) } - MatchType::MostFields => { + MatchType::BoolPrefix => { + let bool_prefix_params: MatchQueryParams = serde_json::from_value(json_val)?; + let bool_prefix = MatchBoolPrefixQuery { + params: bool_prefix_params, + field: field.to_string(), + }; + Ok(ElasticQueryDslInner::MatchBoolPrefix(bool_prefix)) + } + MatchType::MostFields | MatchType::BestFields | MatchType::CrossFields => { let match_query_params: MatchQueryParams = serde_json::from_value(json_val)?; let match_query = MatchQuery { field: field.to_string(), @@ -110,6 +119,7 @@ impl TryFrom for MultiMatchQuery { fn try_from(multi_match_query: MultiMatchQueryForDeserialization) -> Result { if multi_match_query.fields.is_empty() { + // TODO: We can use default field from index configuration instead return Err(serde::de::Error::custom( "Quickwit does not support multi match query with 0 fields. MultiMatchQueries \ must have at least one field.", @@ -139,8 +149,11 @@ impl TryFrom for MultiMatchQuery { pub enum MatchType { #[default] MostFields, + BestFields, // Not implemented will be converted to MostFields + CrossFields, // Not implemented will be converted to MostFields Phrase, PhrasePrefix, + BoolPrefix, } impl ConvertableToQueryAst for MultiMatchQuery { @@ -151,8 +164,8 @@ impl ConvertableToQueryAst for MultiMatchQuery { #[cfg(test)] mod tests { - use super::*; + use crate::elastic_query_dsl::default_max_expansions; #[track_caller] fn test_multimatch_query_ok_aux>(json: &str, expected: T) { @@ -201,6 +214,158 @@ mod tests { .into(), ]), ); + + test_multimatch_query_ok_aux( + r#"{ + "query": "quick brown fox", + "type": "best_fields", + "fields": ["title", "body"] + }"#, + BoolQuery::union(vec![ + MatchQuery { + field: "title".to_string(), + params: MatchQueryParams { + query: "quick brown fox".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + MatchQuery { + field: "body".to_string(), + params: MatchQueryParams { + query: "quick brown fox".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + ]), + ); + + test_multimatch_query_ok_aux( + r#"{ + "query": "quick brown fox", + "type": "cross_fields", + "fields": ["title", "body"] + }"#, + BoolQuery::union(vec![ + MatchQuery { + field: "title".to_string(), + params: MatchQueryParams { + query: "quick brown fox".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + MatchQuery { + field: "body".to_string(), + params: MatchQueryParams { + query: "quick brown fox".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + ]), + ); + + test_multimatch_query_ok_aux( + r#"{ + "query": "quick brown fox", + "type": "phrase", + "fields": ["title", "body"] + }"#, + BoolQuery::union(vec![ + MatchPhraseQuery { + field: "title".to_string(), + params: MatchPhraseQueryParams { + query: "quick brown fox".to_string(), + zero_terms_query: Default::default(), + analyzer: None, + slop: Default::default(), + }, + } + .into(), + MatchPhraseQuery { + field: "body".to_string(), + params: MatchPhraseQueryParams { + query: "quick brown fox".to_string(), + zero_terms_query: Default::default(), + analyzer: None, + slop: Default::default(), + }, + } + .into(), + ]), + ); + + test_multimatch_query_ok_aux( + r#"{ + "query": "quick brown fox", + "type": "phrase_prefix", + "fields": ["title", "body"] + }"#, + BoolQuery::union(vec![ + MatchPhrasePrefixQuery { + field: "title".to_string(), + value: MatchPhrasePrefixQueryParams { + query: "quick brown fox".to_string(), + analyzer: Default::default(), + max_expansions: default_max_expansions(), + slop: Default::default(), + zero_terms_query: Default::default(), + }, + } + .into(), + MatchPhrasePrefixQuery { + field: "body".to_string(), + value: MatchPhrasePrefixQueryParams { + query: "quick brown fox".to_string(), + analyzer: Default::default(), + max_expansions: default_max_expansions(), + slop: Default::default(), + zero_terms_query: Default::default(), + }, + } + .into(), + ]), + ); + + test_multimatch_query_ok_aux( + r#"{ + "query": "quick brown", + "type": "bool_prefix", + "fields": ["title", "body"] + }"#, + BoolQuery::union(vec![ + MatchBoolPrefixQuery { + field: "title".to_string(), + params: MatchQueryParams { + query: "quick brown".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + MatchBoolPrefixQuery { + field: "body".to_string(), + params: MatchQueryParams { + query: "quick brown".to_string(), + operator: crate::BooleanOperand::Or, + zero_terms_query: Default::default(), + _lenient: false, + }, + } + .into(), + ]), + ); } #[test] diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs index 3fe74c28053..5fc1d970f78 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs @@ -40,6 +40,8 @@ pub struct RangeQueryParams { lte: Option, #[serde(default)] boost: Option, + #[serde(default)] + format: Option, } pub type RangeQuery = OneFieldMap; @@ -53,6 +55,7 @@ impl ConvertableToQueryAst for RangeQuery { lt, lte, boost, + format: _, } = self.value; let range_query_ast = crate::query_ast::RangeQuery { field, diff --git a/quickwit/quickwit-serve/src/elasticsearch_api/model/search_body.rs b/quickwit/quickwit-serve/src/elasticsearch_api/model/search_body.rs index 51b7296d516..d6e0d8282c9 100644 --- a/quickwit/quickwit-serve/src/elasticsearch_api/model/search_body.rs +++ b/quickwit/quickwit-serve/src/elasticsearch_api/model/search_body.rs @@ -66,9 +66,28 @@ struct FieldSortParams { pub date_format: Option, } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct SourceObject { + includes: Option>, + excludes: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(untagged)] +pub enum Source { + Bool(bool), + String(String), + List(Vec), + Object(SourceObject), +} + #[derive(Debug, Default, Clone, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] pub struct SearchBody { + #[serde(default)] + pub _source: Option, + #[serde(default)] + pub docvalue_fields: Option, #[serde(default)] pub from: Option, #[serde(default)] @@ -86,6 +105,12 @@ pub struct SearchBody { pub stored_fields: Option>, #[serde(default)] pub search_after: Vec, + #[serde(default)] + pub script_fields: Option, + #[serde(default)] + pub highlight: Option, + #[serde(default)] + pub version: Option, } struct FieldSortVecVisitor; @@ -265,8 +290,9 @@ mod tests { let error_msg = search_body.unwrap_err().to_string(); assert!(error_msg.contains("unknown field `term`")); assert!(error_msg.contains( - "expected one of `from`, `size`, `query`, `sort`, `aggs`, `track_total_hits`, \ - `stored_fields`, `search_after`" + "expected one of `_source`, `docvalue_fields`, `from`, `size`, `query`, `sort`, \ + `aggs`, `track_total_hits`, `stored_fields`, `search_after`, `script_fields`, \ + `highlight`, `version`" )); } }