Skip to main content

mant_query/scope_query/
search.rs

1//! Global search pagination over validated, already-loaded snapshots.
2use super::{QueryScopeView, ScopeExecutionError};
3use mant_protocol::{ScopeSearch, ScopedSearchDocument, SearchQuery};
4
5/// Search existing snapshots with one global result cursor.
6/// No loading or parsing occurs; coverage remains in the supplied graph.
7///
8/// # Errors
9/// Returns invalid search bounds or matcher errors.
10pub fn search_scope(
11    input: QueryScopeView<'_>,
12    query: &SearchQuery,
13) -> Result<ScopeSearch, ScopeExecutionError> {
14    let plan = crate::search::SearchPlan::new(query).map_err(ScopeExecutionError::Search)?;
15    let mut total = 0_u32;
16    let mut remaining_skip = query.offset;
17    let mut remaining_take = query.limit;
18    let mut groups = Vec::new();
19    for (scoped, bundle) in input.iter() {
20        let document_ordinal_base = total;
21        let local = plan
22            .execute(bundle, remaining_skip, remaining_take.max(1))
23            .map_err(ScopeExecutionError::Search)?;
24        total = total.saturating_add(local.total);
25        remaining_skip = remaining_skip.saturating_sub(local.total);
26        if remaining_take == 0 || local.matches.is_empty() {
27            continue;
28        }
29        let mut local = local;
30        let mut hits = std::mem::take(&mut local.matches);
31        if u32::try_from(hits.len()).unwrap_or(u32::MAX) > remaining_take {
32            hits.truncate(usize::try_from(remaining_take).unwrap_or(usize::MAX));
33        }
34        for hit in &mut hits {
35            hit.ordinal = document_ordinal_base.saturating_add(hit.ordinal);
36        }
37        remaining_take =
38            remaining_take.saturating_sub(u32::try_from(hits.len()).unwrap_or(u32::MAX));
39        groups.push(ScopedSearchDocument {
40            address: scoped.address.clone(),
41            depth: scoped.depth,
42            render: local.render,
43            matches: hits,
44        });
45    }
46    let returned = query.limit.saturating_sub(remaining_take);
47    let end = query.offset.saturating_add(returned);
48    Ok(ScopeSearch {
49        query: query.clone(),
50        total,
51        returned,
52        offset: query.offset,
53        truncated: end < total,
54        next_offset: (end < total).then_some(end),
55        documents: groups,
56    })
57}