rs_es/query/
mod.rs

1/*
2 * Copyright 2015-2019 Ben Ashford
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Implementation of the ElasticSearch Query DSL.
18//!
19//! ElasticSearch offers a
20//! [rich DSL for searches](https://www.elastic.co/guide/en/elasticsearch/reference/1.x/query-dsl.html).
21//! It is JSON based, and therefore very easy to use and composable if using from a
22//! dynamic language (e.g.
23//! [Ruby](https://github.com/elastic/elasticsearch-ruby/tree/master/elasticsearch-dsl#features-overview));
24//! but Rust, being a staticly-typed language, things are different.  The `rs_es::query`
25//! module defines a set of builder objects which can be similarly composed to the same
26//! ends.
27//!
28//! For example:
29//!
30//! ```rust
31//! use rs_es::query::Query;
32//!
33//! let query = Query::build_bool()
34//!     .with_must(vec![Query::build_term("field_a",
35//!                                       "value").build(),
36//!                     Query::build_range("field_b")
37//!                           .with_gte(5)
38//!                           .with_lt(10)
39//!                           .build()])
40//!     .build();
41//! ```
42
43use std::collections::BTreeMap;
44
45use serde::ser::{SerializeMap, Serializer};
46use serde::Serialize;
47
48use crate::{json::ShouldSkip, util::StrJoin};
49
50#[macro_use]
51mod common;
52
53pub mod compound;
54pub mod full_text;
55pub mod functions;
56pub mod geo;
57pub mod joining;
58pub mod specialized;
59pub mod term;
60
61// Miscellaneous types required by queries go here
62
63// Enums
64
65/// Minimum should match - used in numerous queries
66/// TODO: should go somewhere specific
67#[derive(Debug)]
68pub struct CombinationMinimumShouldMatch {
69    first: MinimumShouldMatch,
70    second: MinimumShouldMatch,
71}
72
73impl CombinationMinimumShouldMatch {
74    pub fn new<A, B>(first: A, second: B) -> CombinationMinimumShouldMatch
75    where
76        A: Into<MinimumShouldMatch>,
77        B: Into<MinimumShouldMatch>,
78    {
79        CombinationMinimumShouldMatch {
80            first: first.into(),
81            second: second.into(),
82        }
83    }
84}
85
86impl ToString for CombinationMinimumShouldMatch {
87    fn to_string(&self) -> String {
88        format!("{}<{}", self.first.to_string(), self.second.to_string())
89    }
90}
91
92impl Serialize for CombinationMinimumShouldMatch {
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        self.to_string().serialize(serializer)
98    }
99}
100
101#[derive(Debug)]
102pub enum MinimumShouldMatch {
103    Integer(i64),
104    Percentage(f64),
105    Combination(Box<CombinationMinimumShouldMatch>),
106    MultipleCombination(Vec<CombinationMinimumShouldMatch>),
107    LowHigh(i64, i64),
108}
109
110from!(i64, MinimumShouldMatch, Integer);
111from!(f64, MinimumShouldMatch, Percentage);
112from_exp!(
113    CombinationMinimumShouldMatch,
114    MinimumShouldMatch,
115    from,
116    MinimumShouldMatch::Combination(Box::new(from))
117);
118from!(
119    Vec<CombinationMinimumShouldMatch>,
120    MinimumShouldMatch,
121    MultipleCombination
122);
123from_exp!(
124    (i64, i64),
125    MinimumShouldMatch,
126    from,
127    MinimumShouldMatch::LowHigh(from.0, from.1)
128);
129
130impl ToString for MinimumShouldMatch {
131    fn to_string(&self) -> String {
132        match self {
133            MinimumShouldMatch::Integer(val) => val.to_string(),
134            MinimumShouldMatch::Percentage(val) => format!("{}%", val),
135            _ => panic!("Can't convert {:?} to String", self),
136        }
137    }
138}
139
140impl Serialize for MinimumShouldMatch {
141    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142    where
143        S: Serializer,
144    {
145        match self {
146            MinimumShouldMatch::Integer(val) => val.serialize(serializer),
147            MinimumShouldMatch::Percentage(_) => self.to_string().serialize(serializer),
148            MinimumShouldMatch::Combination(ref comb) => comb.serialize(serializer),
149            MinimumShouldMatch::MultipleCombination(ref combs) => combs
150                .iter()
151                .map(ToString::to_string)
152                .join(" ")
153                .serialize(serializer),
154            MinimumShouldMatch::LowHigh(low, high) => {
155                let mut d = BTreeMap::new();
156                d.insert("low_freq", low);
157                d.insert("high_freq", high);
158                d.serialize(serializer)
159            }
160        }
161    }
162}
163
164/// Fuzziness
165#[derive(Debug)]
166pub enum Fuzziness {
167    Auto,
168    LevenshteinDistance(i64),
169    Proportionate(f64),
170}
171
172from!(i64, Fuzziness, LevenshteinDistance);
173from!(f64, Fuzziness, Proportionate);
174
175impl Serialize for Fuzziness {
176    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177    where
178        S: Serializer,
179    {
180        use self::Fuzziness::*;
181        match self {
182            Auto => "auto".serialize(serializer),
183            LevenshteinDistance(dist) => dist.serialize(serializer),
184            Proportionate(p) => p.serialize(serializer),
185        }
186    }
187}
188
189// Flags
190
191/// Flags - multiple operations can take a set of flags, each set is dependent
192/// on the operation in question, but they're all formatted to a similar looking
193/// String
194#[derive(Debug)]
195pub struct Flags<A>(Vec<A>)
196where
197    A: AsRef<str>;
198
199impl<A> Serialize for Flags<A>
200where
201    A: AsRef<str>,
202{
203    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
204    where
205        S: Serializer,
206    {
207        self.0.iter().join("|").serialize(serializer)
208    }
209}
210
211impl<A> From<Vec<A>> for Flags<A>
212where
213    A: AsRef<str>,
214{
215    fn from(from: Vec<A>) -> Self {
216        Flags(from)
217    }
218}
219
220/// ScoreMode
221#[derive(Debug)]
222pub enum ScoreMode {
223    Multiply,
224    Sum,
225    Avg,
226    First,
227    Max,
228    Min,
229}
230
231impl Serialize for ScoreMode {
232    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233    where
234        S: Serializer,
235    {
236        match self {
237            ScoreMode::Multiply => "multiply".serialize(serializer),
238            ScoreMode::Sum => "sum".serialize(serializer),
239            ScoreMode::Avg => "avg".serialize(serializer),
240            ScoreMode::First => "first".serialize(serializer),
241            ScoreMode::Max => "max".serialize(serializer),
242            ScoreMode::Min => "min".serialize(serializer),
243        }
244    }
245}
246
247/// Query represents all available queries
248///
249/// Each value is boxed as Queries can be recursive, they also vary
250/// significantly in size
251
252// TODO: Filters and Queries are merged, ensure all filters are included in this enum
253#[derive(Debug)]
254pub enum Query {
255    MatchAll(Box<MatchAllQuery>),
256
257    // Full-text queries
258    Match(Box<full_text::MatchQuery>),
259    MultiMatch(Box<full_text::MultiMatchQuery>),
260    Common(Box<full_text::CommonQuery>),
261    QueryString(Box<full_text::QueryStringQuery>),
262    SimpleQueryString(Box<full_text::SimpleQueryStringQuery>),
263
264    // Term level queries
265    Term(Box<term::TermQuery>),
266    Terms(Box<term::TermsQuery>),
267    Range(Box<term::RangeQuery>),
268    Exists(Box<term::ExistsQuery>),
269    // Not implementing the Missing query, as it's deprecated, use `must_not` and `Exists`
270    // instead
271    Prefix(Box<term::PrefixQuery>),
272    Wildcard(Box<term::WildcardQuery>),
273    Regexp(Box<term::RegexpQuery>),
274    Fuzzy(Box<term::FuzzyQuery>),
275    Type(Box<term::TypeQuery>),
276    Ids(Box<term::IdsQuery>),
277
278    // Compound queries
279    ConstantScore(Box<compound::ConstantScoreQuery>),
280    Bool(Box<compound::BoolQuery>),
281    DisMax(Box<compound::DisMaxQuery>),
282    FunctionScore(Box<compound::FunctionScoreQuery>),
283    Boosting(Box<compound::BoostingQuery>),
284    Indices(Box<compound::IndicesQuery>),
285    // Not implementing the And query, as it's deprecated, use `bool` instead.
286    // Not implementing the Not query, as it's deprecated
287    // Not implementing the Or query, as it's deprecated, use `bool` instead.
288    // Not implementing the Filtered query, as it's deprecated.
289    // Not implementing the Limit query, as it's deprecated.
290
291    // Joining queries
292    Nested(Box<joining::NestedQuery>),
293    HasChild(Box<joining::HasChildQuery>),
294    HasParent(Box<joining::HasParentQuery>),
295
296    // Geo queries
297    GeoShape(Box<geo::GeoShapeQuery>),
298    GeoBoundingBox(Box<geo::GeoBoundingBoxQuery>),
299    GeoDistance(Box<geo::GeoDistanceQuery>),
300    // TODO: implement me - pending changes to range query
301    //GeoDistanceRange(Box<geo::GeoDistanceRangeQuery>)
302    GeoPolygon(Box<geo::GeoPolygonQuery>),
303    GeohashCell(Box<geo::GeohashCellQuery>),
304
305    // Specialized queries
306    MoreLikeThis(Box<specialized::MoreLikeThisQuery>),
307    // TODO: template queries
308    // TODO: Search by script
309
310    // Span queries
311    // TODO: SpanTerm(Box<term::TermQuery>),
312    // TODO: Span multi term query
313    // TODO: Span first query
314    // TODO: Span near query
315    // TODO: Span or query
316    // TODO: Span not query
317    // TODO: Span containing query
318    // TODO: Span within query
319}
320
321impl Default for Query {
322    fn default() -> Query {
323        Query::MatchAll(Default::default())
324    }
325}
326
327impl Serialize for Query {
328    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
329    where
330        S: Serializer,
331    {
332        use self::Query::*;
333
334        let mut map_ser = serializer.serialize_map(Some(1))?;
335        (match self {
336            // All
337            MatchAll(ref q) => map_ser.serialize_entry("match_all", q),
338
339            // Full-text
340            Match(ref q) => map_ser.serialize_entry("match", q),
341            MultiMatch(ref q) => map_ser.serialize_entry("multi_match", q),
342            Common(ref q) => map_ser.serialize_entry("common", q),
343            QueryString(ref q) => map_ser.serialize_entry("query_string", q),
344            SimpleQueryString(ref q) => map_ser.serialize_entry("simple_query_string", q),
345
346            // Term
347            Term(ref q) => map_ser.serialize_entry("term", q),
348            Terms(ref q) => map_ser.serialize_entry("terms", q),
349            Range(ref q) => map_ser.serialize_entry("range", q),
350            Exists(ref q) => map_ser.serialize_entry("exists", q),
351            Prefix(ref q) => map_ser.serialize_entry("prefix", q),
352            Wildcard(ref q) => map_ser.serialize_entry("wildcard", q),
353            Regexp(ref q) => map_ser.serialize_entry("regexp", q),
354            Fuzzy(ref q) => map_ser.serialize_entry("fuzzy", q),
355            Type(ref q) => map_ser.serialize_entry("type", q),
356            Ids(ref q) => map_ser.serialize_entry("ids", q),
357
358            // Compound
359            ConstantScore(ref q) => map_ser.serialize_entry("constant_score", q),
360            Bool(ref q) => map_ser.serialize_entry("bool", q),
361            DisMax(ref q) => map_ser.serialize_entry("dis_max", q),
362            FunctionScore(ref q) => map_ser.serialize_entry("function_score", q),
363            Boosting(ref q) => map_ser.serialize_entry("boosting", q),
364            Indices(ref q) => map_ser.serialize_entry("indices", q),
365
366            // Joining
367            Nested(ref q) => map_ser.serialize_entry("nested", q),
368            HasChild(ref q) => map_ser.serialize_entry("has_child", q),
369            HasParent(ref q) => map_ser.serialize_entry("has_parent", q),
370
371            // Geo
372            GeoShape(ref q) => map_ser.serialize_entry("geo_shape", q),
373            GeoBoundingBox(ref q) => map_ser.serialize_entry("geo_bounding_box", q),
374            GeoDistance(ref q) => map_ser.serialize_entry("geo_distance", q),
375            GeoPolygon(ref q) => map_ser.serialize_entry("geo_polygon", q),
376            GeohashCell(ref q) => map_ser.serialize_entry("geohash_cell", q),
377
378            // Specialized
379            MoreLikeThis(ref q) => map_ser.serialize_entry("more_like_this", q),
380        })?;
381        map_ser.end()
382    }
383}
384
385// Specific query types go here
386
387/// Match all query
388
389#[derive(Debug, Default, Serialize)]
390pub struct MatchAllQuery {
391    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
392    boost: Option<f64>,
393}
394
395impl Query {
396    pub fn build_match_all() -> MatchAllQuery {
397        MatchAllQuery::default()
398    }
399}
400
401impl MatchAllQuery {
402    add_field!(with_boost, boost, f64);
403
404    build!(MatchAll);
405}
406
407#[cfg(test)]
408mod tests {
409    extern crate serde_json;
410
411    use super::full_text::SimpleQueryStringFlags;
412    use super::functions::Function;
413    use super::term::TermsQueryLookup;
414    use super::{Flags, Query};
415
416    #[test]
417    fn test_simple_query_string_flags() {
418        let opts = vec![SimpleQueryStringFlags::And, SimpleQueryStringFlags::Not];
419        let flags: Flags<SimpleQueryStringFlags> = opts.into();
420        let json = serde_json::to_string(&flags);
421        assert_eq!("\"AND|NOT\"", json.unwrap());
422    }
423
424    #[test]
425    fn test_terms_query() {
426        let terms_query = Query::build_terms("field_name")
427            .with_values(vec!["a", "b", "c"])
428            .build();
429        assert_eq!(
430            "{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
431            serde_json::to_string(&terms_query).unwrap()
432        );
433
434        let terms_query_2 = Query::build_terms("field_name")
435            .with_values(["a", "b", "c"].as_ref())
436            .build();
437        assert_eq!(
438            "{\"terms\":{\"field_name\":[\"a\",\"b\",\"c\"]}}",
439            serde_json::to_string(&terms_query_2).unwrap()
440        );
441
442        let terms_query_3 = Query::build_terms("field_name")
443            .with_values(TermsQueryLookup::new(123, "blah.de.blah").with_index("other"))
444            .build();
445        assert_eq!("{\"terms\":{\"field_name\":{\"id\":123,\"index\":\"other\",\"path\":\"blah.de.blah\"}}}",
446                   serde_json::to_string(&terms_query_3).unwrap());
447    }
448
449    #[test]
450    fn test_function_score_query() {
451        let function_score_query = Query::build_function_score()
452            .with_function(
453                Function::build_script_score("this_is_a_script")
454                    .with_lang("made_up")
455                    .add_param("A", 12)
456                    .build(),
457            )
458            .build();
459        assert_eq!("{\"function_score\":{\"functions\":[{\"script_score\":{\"lang\":\"made_up\",\"params\":{\"A\":12},\"inline\":\"this_is_a_script\"}}]}}",
460                   serde_json::to_string(&function_score_query).unwrap());
461    }
462
463    #[test]
464    fn test_exists_query() {
465        let exists_query = Query::build_exists("name").build();
466        assert_eq!(
467            "{\"exists\":{\"field\":\"name\"}}",
468            serde_json::to_string(&exists_query).unwrap()
469        );
470    }
471}