diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/string_or_struct.rs b/quickwit/quickwit-query/src/elastic_query_dsl/literal_or_struct.rs similarity index 75% rename from quickwit/quickwit-query/src/elastic_query_dsl/string_or_struct.rs rename to quickwit/quickwit-query/src/elastic_query_dsl/literal_or_struct.rs index c2be03c8367..c290ef3a6c0 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/string_or_struct.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/literal_or_struct.rs @@ -18,7 +18,7 @@ use std::marker::PhantomData; use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer, de}; -/// The point of `StringOrStructForSerialization` is to support +/// The point of `LiteralOrStructForSerialization` is to support /// the two following formats for various queries. /// /// `{"field": {"query": "my query", "default_operator": "OR"}}` @@ -26,37 +26,37 @@ use serde::{Deserialize, Deserializer, de}; /// and the shorter. /// `{"field": "my query"}` /// -/// If a integer is passed, we cast it to string. Floats are not supported. +/// If a number or bool is passed, we cast it to string /// /// We don't use untagged enum to support this, in order to keep good errors. /// /// The code below is adapted from solution described here: #[derive(Deserialize)] #[serde(transparent)] -pub(crate) struct StringOrStructForSerialization +pub(crate) struct LiteralOrStructForSerialization where T: From, for<'de2> T: Deserialize<'de2>, { - #[serde(deserialize_with = "string_or_struct")] + #[serde(deserialize_with = "literal_or_struct")] pub inner: T, } -struct StringOrStructVisitor { +struct LiteralOrStructVisitor { phantom_data: PhantomData, } -fn string_or_struct<'de, D, T>(deserializer: D) -> Result +fn literal_or_struct<'de, D, T>(deserializer: D) -> Result where D: Deserializer<'de>, T: From + Deserialize<'de>, { - deserializer.deserialize_any(StringOrStructVisitor { + deserializer.deserialize_any(LiteralOrStructVisitor { phantom_data: Default::default(), }) } -impl<'de, T> Visitor<'de> for StringOrStructVisitor +impl<'de, T> Visitor<'de> for LiteralOrStructVisitor where T: From, T: Deserialize<'de>, @@ -68,6 +68,11 @@ where formatter.write_str(&format!("string or map to deserialize {type_str}.")) } + fn visit_bool(self, v: bool) -> Result + where E: de::Error { + self.visit_str(&v.to_string()) + } + fn visit_i64(self, v: i64) -> Result where E: de::Error { self.visit_str(&v.to_string()) @@ -78,6 +83,11 @@ where self.visit_str(&v.to_string()) } + fn visit_f64(self, v: f64) -> Result + where E: de::Error { + self.visit_str(&v.to_string()) + } + fn visit_str(self, query: &str) -> Result where E: serde::de::Error { Ok(T::from(query.to_string())) 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 752f4f2c0a2..dbb2491903b 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 @@ -14,7 +14,7 @@ use serde::Deserialize; -use super::{ElasticQueryDslInner, StringOrStructForSerialization}; +use super::{ElasticQueryDslInner, LiteralOrStructForSerialization}; use crate::OneFieldMap; use crate::elastic_query_dsl::match_query::MatchQueryParams; use crate::elastic_query_dsl::{ConvertibleToQueryAst, default_max_expansions}; @@ -23,7 +23,7 @@ use crate::query_ast::{FullTextParams, FullTextQuery, QueryAst}; /// `MatchBoolPrefixQuery` as defined in /// #[derive(Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub(crate) struct MatchBoolPrefixQuery { pub(crate) field: String, pub(crate) params: MatchQueryParams, @@ -54,9 +54,9 @@ impl From for ElasticQueryDslInner { } } -impl From>> for MatchBoolPrefixQuery { +impl From>> for MatchBoolPrefixQuery { fn from( - match_query_params: OneFieldMap>, + match_query_params: OneFieldMap>, ) -> Self { let OneFieldMap { field, value } = match_query_params; MatchBoolPrefixQuery { 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 1f49929782f..e178a053ea1 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 @@ -15,7 +15,7 @@ use serde::Deserialize; use crate::elastic_query_dsl::{ - ConvertibleToQueryAst, ElasticQueryDslInner, StringOrStructForSerialization, + ConvertibleToQueryAst, ElasticQueryDslInner, LiteralOrStructForSerialization, }; use crate::query_ast::{FullTextMode, FullTextParams, FullTextQuery, QueryAst}; use crate::{MatchAllOrNone, OneFieldMap}; @@ -23,7 +23,7 @@ use crate::{MatchAllOrNone, OneFieldMap}; /// `MatchPhraseQuery` as defined in /// #[derive(Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub(crate) struct MatchPhraseQuery { pub(crate) field: String, pub(crate) params: MatchPhraseQueryParams, @@ -65,11 +65,11 @@ impl From for ElasticQueryDslInner { } } -impl From>> +impl From>> for MatchPhraseQuery { fn from( - match_query_params: OneFieldMap>, + match_query_params: OneFieldMap>, ) -> Self { let OneFieldMap { field, value } = match_query_params; MatchPhraseQuery { diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/match_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/match_query.rs index 9f0f56a3184..5286c9736e0 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/match_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/match_query.rs @@ -16,7 +16,7 @@ use serde::Deserialize; use super::LeniencyBool; use crate::elastic_query_dsl::{ - ConvertibleToQueryAst, ElasticQueryDslInner, StringOrStructForSerialization, + ConvertibleToQueryAst, ElasticQueryDslInner, LiteralOrStructForSerialization, }; use crate::query_ast::{FullTextParams, FullTextQuery, QueryAst}; use crate::{BooleanOperand, MatchAllOrNone, OneFieldMap}; @@ -24,7 +24,7 @@ use crate::{BooleanOperand, MatchAllOrNone, OneFieldMap}; /// `MatchQuery` as defined in /// #[derive(Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub struct MatchQuery { pub(crate) field: String, pub(crate) params: MatchQueryParams, @@ -64,9 +64,9 @@ impl From for ElasticQueryDslInner { } } -impl From>> for MatchQuery { +impl From>> for MatchQuery { fn from( - match_query_params: OneFieldMap>, + match_query_params: OneFieldMap>, ) -> Self { let OneFieldMap { field, value } = match_query_params; MatchQuery { diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/mod.rs b/quickwit/quickwit-query/src/elastic_query_dsl/mod.rs index 871032951e2..e4af368534d 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/mod.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/mod.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; mod bool_query; mod exists_query; +mod literal_or_struct; mod match_bool_prefix; mod match_phrase_query; mod match_query; @@ -26,18 +27,17 @@ mod prefix_query; mod query_string_query; mod range_query; mod regex_query; -mod string_or_struct; mod term_query; mod terms_query; mod wildcard_query; use bool_query::BoolQuery; +pub(crate) use literal_or_struct::LiteralOrStructForSerialization; pub use one_field_map::OneFieldMap; use phrase_prefix_query::MatchPhrasePrefixQuery; use prefix_query::PrefixQuery; pub(crate) use query_string_query::QueryStringQuery; use range_query::RangeQuery; -pub(crate) use string_or_struct::StringOrStructForSerialization; use term_query::TermQuery; use crate::elastic_query_dsl::exists_query::ExistsQuery; diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/prefix_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/prefix_query.rs index f19fad61037..0542c9de954 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/prefix_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/prefix_query.rs @@ -15,11 +15,11 @@ use serde::Deserialize; use crate::elastic_query_dsl::one_field_map::OneFieldMap; -use crate::elastic_query_dsl::{ConvertibleToQueryAst, StringOrStructForSerialization}; +use crate::elastic_query_dsl::{ConvertibleToQueryAst, LiteralOrStructForSerialization}; use crate::query_ast::{QueryAst, WildcardQuery as AstWildcardQuery}; #[derive(Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub(crate) struct PrefixQuery { pub(crate) field: String, pub(crate) params: PrefixQueryParams, @@ -53,9 +53,9 @@ impl ConvertibleToQueryAst for PrefixQuery { } } -impl From>> for PrefixQuery { +impl From>> for PrefixQuery { fn from( - match_query_params: OneFieldMap>, + match_query_params: OneFieldMap>, ) -> Self { let OneFieldMap { field, value } = match_query_params; PrefixQuery { diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/term_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/term_query.rs index 5fd320a2580..d8b34a5ebbc 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/term_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/term_query.rs @@ -14,21 +14,21 @@ use serde::{Deserialize, Deserializer, Serialize}; -use super::StringOrStructForSerialization; +use super::LiteralOrStructForSerialization; use crate::elastic_query_dsl::one_field_map::OneFieldMap; use crate::elastic_query_dsl::{ConvertibleToQueryAst, ElasticQueryDslInner}; use crate::not_nan_f32::NotNaNf32; use crate::query_ast::{self, QueryAst}; #[derive(Deserialize, Debug, PartialEq, Eq, Clone)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub struct TermQuery { pub field: String, pub value: TermQueryParams, } -impl From>> for TermQuery { - fn from(one_field_map: OneFieldMap>) -> Self { +impl From>> for TermQuery { + fn from(one_field_map: OneFieldMap>) -> Self { TermQuery { field: one_field_map.field, value: one_field_map.value.inner, @@ -52,6 +52,8 @@ enum TermValue { I64(i64), U64(u64), Str(String), + Bool(bool), + F64(f64), } fn deserialize_term_value<'de, D>(deserializer: D) -> Result @@ -61,6 +63,8 @@ where D: Deserializer<'de> { TermValue::I64(i64) => Ok(i64.to_string()), TermValue::U64(u64) => Ok(u64.to_string()), TermValue::Str(str) => Ok(str), + TermValue::Bool(b) => Ok(b.to_string()), + TermValue::F64(f) => Ok(f.to_string()), } } @@ -123,7 +127,7 @@ mod tests { use super::*; #[test] - fn test_term_query_simple() { + fn test_term_query_string() { let term_query_json = r#"{ "product_id": { "value": "61809" } }"#; let term_query: TermQuery = serde_json::from_str(term_query_json).unwrap(); assert_eq!( @@ -133,7 +137,7 @@ mod tests { } #[test] - fn test_term_query_deserialization_in_short_format() { + fn test_term_query_string_short_form() { let term_query: TermQuery = serde_json::from_str( r#"{ "product_id": "61809" @@ -145,4 +149,38 @@ mod tests { &term_query_from_field_value("product_id", "61809") ); } + + #[test] + fn test_term_query_bool() { + let term_query_json = r#"{ "is_product_pretty": { "value": true } }"#; + let term_query: TermQuery = serde_json::from_str(term_query_json).unwrap(); + assert_eq!( + &term_query, + &term_query_from_field_value("is_product_pretty", "true") + ); + } + + #[test] + fn test_term_query_bool_short_form() { + let term_query_json = r#"{ "is_product_pretty": true }"#; + let term_query: TermQuery = serde_json::from_str(term_query_json).unwrap(); + assert_eq!( + &term_query, + &term_query_from_field_value("is_product_pretty", "true") + ); + } + + #[test] + fn test_term_query_float() { + let term_query_json = r#"{ "price": { "value": 1.1 } }"#; + let term_query: TermQuery = serde_json::from_str(term_query_json).unwrap(); + assert_eq!(&term_query, &term_query_from_field_value("price", "1.1")); + } + + #[test] + fn test_term_query_float_short_form() { + let term_query_json = r#"{ "price": 1.1 }"#; + let term_query: TermQuery = serde_json::from_str(term_query_json).unwrap(); + assert_eq!(&term_query, &term_query_from_field_value("price", "1.1")); + } } diff --git a/quickwit/quickwit-query/src/elastic_query_dsl/wildcard_query.rs b/quickwit/quickwit-query/src/elastic_query_dsl/wildcard_query.rs index 3b975e896e9..5fef8c667a8 100644 --- a/quickwit/quickwit-query/src/elastic_query_dsl/wildcard_query.rs +++ b/quickwit/quickwit-query/src/elastic_query_dsl/wildcard_query.rs @@ -16,11 +16,11 @@ use serde::Deserialize; use crate::NotNaNf32; use crate::elastic_query_dsl::one_field_map::OneFieldMap; -use crate::elastic_query_dsl::{ConvertibleToQueryAst, StringOrStructForSerialization}; +use crate::elastic_query_dsl::{ConvertibleToQueryAst, LiteralOrStructForSerialization}; use crate::query_ast::{QueryAst, WildcardQuery as AstWildcardQuery}; #[derive(Deserialize, Clone, Eq, PartialEq, Debug)] -#[serde(from = "OneFieldMap>")] +#[serde(from = "OneFieldMap>")] pub(crate) struct WildcardQuery { pub(crate) field: String, pub(crate) params: WildcardQueryParams, @@ -49,9 +49,9 @@ impl ConvertibleToQueryAst for WildcardQuery { } } -impl From>> for WildcardQuery { +impl From>> for WildcardQuery { fn from( - match_query_params: OneFieldMap>, + match_query_params: OneFieldMap>, ) -> Self { let OneFieldMap { field, value } = match_query_params; WildcardQuery { diff --git a/quickwit/rest-api-tests/scenarii/es_compatibility/0006-term_query.yaml b/quickwit/rest-api-tests/scenarii/es_compatibility/0006-term_query.yaml index 3fe75d61973..9a691dbb75e 100644 --- a/quickwit/rest-api-tests/scenarii/es_compatibility/0006-term_query.yaml +++ b/quickwit/rest-api-tests/scenarii/es_compatibility/0006-term_query.yaml @@ -61,7 +61,6 @@ expected: relation: "eq" # Also testing numbers, and numbers as string in the JSON query --- -engines: ["elasticsearch"] params: size: 0 json: @@ -170,3 +169,57 @@ expected: total: value: 1 relation: "eq" +--- +params: + size: 0 +json: + track_total_hits: true + query: + term: + payload.commits.distinct: "true" +expected: + hits: + total: + value: 60 + relation: "eq" +--- +params: + size: 0 +json: + track_total_hits: true + query: + term: + payload.commits.distinct: true +expected: + hits: + total: + value: 60 + relation: "eq" +--- +endpoint: "simple_es_compat/_search" +params: + size: 0 +json: + track_total_hits: true + query: + term: + float_field: "1.1" +expected: + hits: + total: + value: 1 + relation: "eq" +--- +endpoint: "simple_es_compat/_search" +params: + size: 0 +json: + track_total_hits: true + query: + term: + float_field: 1.1 +expected: + hits: + total: + value: 1 + relation: "eq" \ No newline at end of file diff --git a/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.elasticsearch.yaml b/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.elasticsearch.yaml index 22d847c8679..553b94d7c81 100644 --- a/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.elasticsearch.yaml +++ b/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.elasticsearch.yaml @@ -154,6 +154,6 @@ params: headers: {"Content-Type": "application/json"} ndjson: - {"index":{"_index":"simple_es_compat"}} - - {"keyword_text": "red"} + - {"keyword_text": "red", "float_field": 1.1} - {"index":{"_index":"simple_es_compat"}} - - {"keyword_text": "gold$"} + - {"keyword_text": "gold$", "float_field": 2.2} diff --git a/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.quickwit.yaml b/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.quickwit.yaml index f84d3587fed..0e1f76b3b28 100644 --- a/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.quickwit.yaml +++ b/quickwit/rest-api-tests/scenarii/es_compatibility/_setup.quickwit.yaml @@ -135,5 +135,5 @@ endpoint: simple_es_compat/ingest params: commit: force ndjson: - - {"keyword_text": "red"} - - {"keyword_text": "gold$"} + - {"keyword_text": "red", "float_field": 1.1} + - {"keyword_text": "gold$", "float_field": 2.2}