Skip to main content

flusso_query/handles/
nested.rs

1//! Handles for `nested` arrays of objects: [`Nested`] (lifting element queries
2//! into the enclosing scope) and [`NestedProjection`] (shaping the returned
3//! array via `inner_hits`).
4
5use std::marker::PhantomData;
6
7use serde_json::{Map, Value};
8
9use super::{Common, NestedScoreMode, Sort, common_opts, exists_q, match_all_value};
10use crate::query::{AsQuery, Query, Root};
11
12/// `{ "nested": { "path": "<path>", "query": <query> } }`.
13fn nested_value(path: &str, query: Value) -> Value {
14    let mut body = Map::new();
15    body.insert("path".to_string(), Value::String(path.to_string()));
16    body.insert("query".to_string(), query);
17    let mut outer = Map::new();
18    outer.insert("nested".to_string(), Value::Object(body));
19    Value::Object(outer)
20}
21
22/// `{ "bool": { "<clause>": [ … ] } }`.
23fn bool_value(clause: &str, items: Vec<Value>) -> Value {
24    let mut body = Map::new();
25    body.insert(clause.to_string(), Value::Array(items));
26    let mut outer = Map::new();
27    outer.insert("bool".to_string(), Value::Object(body));
28    Value::Object(outer)
29}
30
31/// A `nested` array of objects. `E` is the **enclosing** scope (where queries
32/// over this array land — `Root` at the top level, the parent element type when
33/// deeper); `C` is the **child** scope (the element type). Lifting a child query
34/// (`Query<C>`) through `any`/`all` produces a `Query<E>`.
35#[derive(Debug, Clone)]
36pub struct Nested<E = Root, C = serde_json::Value> {
37    path: String,
38    _marker: PhantomData<fn() -> (E, C)>,
39}
40
41impl<E, C> Nested<E, C> {
42    pub fn at(path: impl Into<String>) -> Self {
43        Self {
44            path: path.into(),
45            _marker: PhantomData,
46        }
47    }
48
49    /// Parents with **at least one** element matching `query`. Returns a
50    /// [`NestedQuery`] builder for `score_mode` / `ignore_unmapped` plus
51    /// `boost` / `name`.
52    pub fn any(&self, query: impl AsQuery<C>) -> NestedQuery<E> {
53        let inner = query
54            .into_query()
55            .map_or_else(match_all_value, |q| q.to_value());
56        NestedQuery {
57            path: self.path.clone(),
58            query: inner,
59            opts: Map::new(),
60            common: Common::default(),
61            _marker: PhantomData,
62        }
63    }
64
65    /// Parents where **every** element matches `query` ("no element fails it").
66    pub fn all(&self, query: impl AsQuery<C>) -> Query<E> {
67        let inner = query
68            .into_query()
69            .map_or_else(match_all_value, |q| q.to_value());
70        let fails = bool_value("must_not", vec![inner]);
71        let nested = nested_value(&self.path, fails);
72        Query::leaf(bool_value("must_not", vec![nested]))
73    }
74
75    /// The nested array has at least one element.
76    pub fn exists(&self) -> Query<E> {
77        exists_q(&self.path)
78    }
79
80    /// Shape the **returned** array: keep elements matching `query` (with the
81    /// builder's sort/size). Pass to [`crate::Search::filter_nested`].
82    pub fn matching(&self, query: impl AsQuery<C>) -> NestedProjection {
83        NestedProjection {
84            path: self.path.clone(),
85            query: query.into_query().map(|q| q.to_value()),
86            sort: Vec::new(),
87            size: None,
88            from: None,
89        }
90    }
91
92    /// Like [`matching`](Self::matching) with no predicate — every element.
93    pub fn project(&self) -> NestedProjection {
94        NestedProjection {
95            path: self.path.clone(),
96            query: None,
97            sort: Vec::new(),
98            size: None,
99            from: None,
100        }
101    }
102}
103
104/// A `nested` clause (parents with a matching element), with the `score_mode` /
105/// `ignore_unmapped` options plus `boost` / `name`. `E` is the enclosing scope.
106#[derive(Debug, Clone)]
107pub struct NestedQuery<E = Root> {
108    path: String,
109    query: Value,
110    opts: Map<String, Value>,
111    common: Common,
112    _marker: PhantomData<fn() -> E>,
113}
114
115impl<E> NestedQuery<E> {
116    /// How matching elements' scores combine into the parent score
117    /// ([`NestedScoreMode::Avg`] is the default; [`NestedScoreMode::None`]
118    /// makes the clause a pure filter).
119    #[must_use]
120    pub fn score_mode(mut self, score_mode: NestedScoreMode) -> Self {
121        self.opts.insert(
122            "score_mode".to_string(),
123            Value::String(score_mode.as_str().to_string()),
124        );
125        self
126    }
127
128    /// Treat an unmapped `path` as matching nothing instead of erroring.
129    #[must_use]
130    pub fn ignore_unmapped(mut self, ignore_unmapped: bool) -> Self {
131        self.opts
132            .insert("ignore_unmapped".to_string(), Value::Bool(ignore_unmapped));
133        self
134    }
135
136    common_opts!(common);
137}
138
139impl<E> AsQuery<E> for NestedQuery<E> {
140    fn into_query(self) -> Option<Query<E>> {
141        let mut body = self.opts;
142        body.insert("path".to_string(), Value::String(self.path));
143        body.insert("query".to_string(), self.query);
144        self.common.write(&mut body);
145        let mut outer = Map::new();
146        outer.insert("nested".to_string(), Value::Object(body));
147        Some(Query::leaf(Value::Object(outer)))
148    }
149}
150
151/// A request to shape one nested array in the results (via `inner_hits`).
152#[derive(Debug, Clone)]
153pub struct NestedProjection {
154    path: String,
155    query: Option<Value>,
156    sort: Vec<Sort>,
157    size: Option<u64>,
158    from: Option<u64>,
159}
160
161impl NestedProjection {
162    /// Order the returned elements. A field sort keeps just its key + order:
163    /// inside `inner_hits` the sort already runs within the nested document, so
164    /// any `nested` wrapper from a [`Sortable`](crate::Sortable) handle is dropped here.
165    #[must_use]
166    pub fn sort(mut self, sort: Sort) -> Self {
167        self.sort.push(sort.without_nested_context());
168        self
169    }
170
171    /// Order the returned elements by several keys at once — e.g. from a
172    /// [`SortBuilder`](crate::SortBuilder). Equivalent to repeated [`sort`](Self::sort).
173    #[must_use]
174    pub fn sorts(mut self, sorts: impl IntoIterator<Item = Sort>) -> Self {
175        self.sort
176            .extend(sorts.into_iter().map(Sort::without_nested_context));
177        self
178    }
179
180    /// Cap how many elements are returned per parent.
181    #[must_use]
182    pub fn size(mut self, size: u64) -> Self {
183        self.size = Some(size);
184        self
185    }
186
187    /// Offset within each parent's matching elements.
188    #[must_use]
189    pub fn from(mut self, from: u64) -> Self {
190        self.from = Some(from);
191        self
192    }
193
194    pub(crate) fn path(&self) -> &str {
195        &self.path
196    }
197
198    /// The `{ "nested": { path, query, inner_hits } }` clause (inner_hits named
199    /// after the path, for retrieval).
200    pub(crate) fn to_value(&self) -> Value {
201        let query = self.query.clone().unwrap_or_else(match_all_value);
202        let mut inner_hits = Map::new();
203        inner_hits.insert("name".to_string(), Value::String(self.path.clone()));
204        if let Some(size) = self.size {
205            inner_hits.insert("size".to_string(), Value::from(size));
206        }
207        if let Some(from) = self.from {
208            inner_hits.insert("from".to_string(), Value::from(from));
209        }
210        if !self.sort.is_empty() {
211            let keys = self.sort.iter().map(Sort::to_value).collect();
212            inner_hits.insert("sort".to_string(), Value::Array(keys));
213        }
214        let mut nested = Map::new();
215        nested.insert("path".to_string(), Value::String(self.path.clone()));
216        nested.insert("query".to_string(), query);
217        nested.insert("inner_hits".to_string(), Value::Object(inner_hits));
218        let mut outer = Map::new();
219        outer.insert("nested".to_string(), Value::Object(nested));
220        Value::Object(outer)
221    }
222}