1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use crate::search::*;
use crate::util::*;

/// Returns parent documents whose joined child documents match a provided query. You can create
/// parent-child relationships between documents in the same index using a join field mapping.
///
/// To create has_child query:
/// ```
/// # use elasticsearch_dsl::queries::*;
/// # use elasticsearch_dsl::queries::params::*;
/// # let query =
/// Query::has_child("child", Query::term("tag", "elasticsearch"));
/// ```
/// <https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-HasChild-query.html>
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct HasChildQuery {
    #[serde(rename = "has_child")]
    inner: Inner,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
struct Inner {
    r#type: String,

    query: Box<Query>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    ignore_unmapped: Option<bool>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    max_children: Option<u32>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    min_children: Option<u32>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    score_mode: Option<HasChildScoreMode>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    boost: Option<Boost>,

    #[serde(skip_serializing_if = "ShouldSkip::should_skip")]
    _name: Option<String>,
}

impl Query {
    /// Creates an instance of [`HasChildQuery`]
    ///
    /// - `type` - Name of the child relationship mapped for the join field.
    /// - `query` - Query you wish to run on child documents of the `type` field. If a child
    /// document matches the search, the query returns the parent document.
    pub fn has_child(r#type: impl ToString, query: impl Into<Query>) -> HasChildQuery {
        HasChildQuery {
            inner: Inner {
                r#type: r#type.to_string(),
                query: Box::new(query.into()),
                ignore_unmapped: None,
                max_children: None,
                min_children: None,
                score_mode: None,
                boost: None,
                _name: None,
            },
        }
    }
}

impl HasChildQuery {
    /// Indicates whether to ignore an unmapped `type` and not return any documents instead of an
    /// error. Defaults to `false`.
    ///
    /// If `false`, Elasticsearch returns an error if the `type` is unmapped.
    ///
    /// You can use this parameter to query multiple indices that may not contain the `type`.
    pub fn ignore_unmapped(mut self, ignore_unmapped: bool) -> Self {
        self.inner.ignore_unmapped = Some(ignore_unmapped);
        self
    }

    /// Maximum number of child documents that match the `query` allowed for a returned parent
    /// document. If the parent document exceeds this limit, it is excluded from the search results.
    pub fn max_children(mut self, max_children: impl Into<u32>) -> Self {
        self.inner.max_children = Some(max_children.into());
        self
    }

    /// Minimum number of child documents that match the `query` required to match the query for a
    /// returned parent document. If the parent document does not meet this limit, it is excluded
    /// from the search results.
    pub fn min_children(mut self, min_children: impl Into<u32>) -> Self {
        self.inner.min_children = Some(min_children.into());
        self
    }

    /// Indicates how scores for matching child documents affect the root parent document’s
    /// relevance score.
    pub fn score_mode(mut self, score_mode: HasChildScoreMode) -> Self {
        self.inner.score_mode = Some(score_mode);
        self
    }

    add_boost_and_name!();
}

impl ShouldSkip for HasChildQuery {
    fn should_skip(&self) -> bool {
        self.inner.r#type.should_skip() || self.inner.query.should_skip()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    test_serialization! {
        with_required_fields(
            Query::has_child("child", Query::term("tag", "elasticsearch")),
            json!({
                "has_child": {
                    "type": "child",
                    "query": {
                        "term": {
                            "tag": {
                                "value": "elasticsearch"
                            }
                        }
                    }
                }
            })
        );

        with_all_fields(
            Query::has_child("child", Query::term("tag", "elasticsearch"))
                .boost(2)
                .name("test")
                .ignore_unmapped(true)
                .max_children(3u32)
                .min_children(2u32)
                .score_mode(HasChildScoreMode::Max),
            json!({
                "has_child": {
                    "type": "child",
                    "ignore_unmapped": true,
                    "max_children": 3,
                    "min_children": 2,
                    "score_mode": "max",
                    "query": {
                        "term": {
                            "tag": {
                                "value": "elasticsearch"
                            }
                        }
                    },
                    "boost": 2,
                    "_name": "test"
                }
            })
        );
    }
}