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
use crate::search::*;
use crate::util::*;

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

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

    query: Box<Query>,

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

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

    #[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 [`HasParentQuery`]
    ///
    /// - `parent-type` - Name of the parent relationship mapped for the join field.
    /// - `query` - Query you wish to run on parent documents of the `parent_type` field. If a
    /// parent document matches the search, the query returns its child documents.
    pub fn has_parent(parent_type: impl ToString, query: impl Into<Query>) -> HasParentQuery {
        HasParentQuery {
            inner: Inner {
                parent_type: parent_type.to_string(),
                query: Box::new(query.into()),
                score: None,
                ignore_unmapped: None,
                boost: None,
                _name: None,
            },
        }
    }
}

impl HasParentQuery {
    /// Indicates whether the relevance score of a matching parent document is aggregated into its
    /// child documents. Defaults to `false`.
    ///
    /// If `false`, Elasticsearch ignores the relevance score of the parent document. Elasticsearch
    /// also assigns each child document a relevance score equal to the `query`'s `boost`, which
    /// defaults to `1`.
    ///
    /// If `true`, the relevance score of the matching parent document is aggregated into its child
    /// documents' relevance scores.
    pub fn score(mut self, score: bool) -> Self {
        self.inner.score = Some(score);
        self
    }

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

    add_boost_and_name!();
}

impl ShouldSkip for HasParentQuery {
    fn should_skip(&self) -> bool {
        self.inner.parent_type.should_skip() || self.inner.query.should_skip()
    }
}

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

    test_serialization! {
        with_required_fields(
            Query::has_parent("parent", Query::term("tag", "elasticsearch")),
            json!({
                "has_parent": {
                    "parent_type": "parent",
                    "query": {
                        "term": {
                            "tag": {
                                "value": "elasticsearch"
                            }
                        }
                    }
                }
            })
        );

        with_all_fields(
            Query::has_parent("parent", Query::term("tag", "elasticsearch"))
                .boost(2)
                .name("test")
                .ignore_unmapped(true)
                .score(true),
            json!({
                "has_parent": {
                    "parent_type": "parent",
                    "score": true,
                    "ignore_unmapped": true,
                    "query": {
                        "term": {
                            "tag": {
                                "value": "elasticsearch"
                            }
                        }
                    },
                    "boost": 2,
                    "_name": "test"
                }
            })
        );
    }
}