Skip to main content

drive/query/drive_document_count_query/
execute_point_lookup.rs

1//! Equal/In point-lookup execution paths for the count query.
2//!
3//! No-proof and proof executors for fully-covered Equal/`In` queries
4//! against a `countable: true` index. Both sides share the same
5//! [`DriveDocumentCountQuery::point_lookup_count_path_query`] builder,
6//! so the proof bytes the server signs and the path query the verifier
7//! reconstructs (and the no-proof read this file performs) all see
8//! the exact same shape — there's only one source of truth for which
9//! `CountTree` elements compose the answer.
10//!
11//! Range-mode executors live in
12//! [`super::execute_range_count`](super::execute_range_count); this
13//! file is the Equal/In half of the dispatch surface.
14//!
15//! Whole module is gated `feature = "server"` via the parent's
16//! `pub mod execute_point_lookup;` declaration.
17
18use super::{DriveDocumentCountQuery, SplitCountEntry};
19use crate::drive::Drive;
20use crate::error::Error;
21use dpp::version::PlatformVersion;
22use grovedb::query_result_type::{QueryResultElement, QueryResultType};
23use grovedb::TransactionArg;
24use grovedb_costs::CostContext;
25
26impl DriveDocumentCountQuery<'_> {
27    /// Executes the count query without generating a proof.
28    ///
29    /// Returns the total count as a single `SplitCountEntry` with
30    /// empty `key` (the unified-count Total shape).
31    ///
32    /// Implementation goes through the same
33    /// [`Self::point_lookup_count_path_query`] builder the prove
34    /// path uses, then runs `grove.query` to fetch the matched
35    /// `CountTree` elements and sums their `count_value_or_default()`
36    /// values. The builder handles all three structural cases
37    /// (Equal-only fully covered, In at any index position, In with
38    /// trailing Equals via `set_subquery_path`) — there's no need
39    /// for a separate recursive walker on the no-proof side.
40    pub fn execute_no_proof(
41        &self,
42        drive: &Drive,
43        transaction: TransactionArg,
44        platform_version: &PlatformVersion,
45    ) -> Result<Vec<SplitCountEntry>, Error> {
46        let drive_version = &platform_version.drive;
47        let path_query = self.point_lookup_count_path_query(platform_version)?;
48        // `grove_get_path_query` requires a `drive_operations` sink for
49        // cost accounting; the no-proof executor doesn't propagate fees
50        // upward (callers that need cost are the per-mode dispatchers
51        // in `drive_dispatcher.rs`, which wrap this for fee calculation),
52        // so we use a local vec and discard.
53        let mut drive_operations = vec![];
54        let (results, _) = drive.grove_get_path_query(
55            &path_query,
56            transaction,
57            QueryResultType::QueryElementResultType,
58            &mut drive_operations,
59            drive_version,
60        )?;
61        // Sum across emitted CountTree elements:
62        // - Equal-only: 0 or 1 element (0 when the branch is absent).
63        // - In at any position: one element per In branch that has at
64        //   least one doc; missing branches contribute 0 by virtue of
65        //   being absent from the result set.
66        // `count_value_or_default()` returns the `CountTree`'s count
67        // for `Element::CountTree` / `Element::SumTree` and 1 for
68        // `Element::Reference` (the unique-index-with-all-non-null
69        // case — see `Element::count_value_or_default` for the per-
70        // variant contract).
71        let count: u64 = results
72            .elements
73            .iter()
74            .map(|e| match e {
75                QueryResultElement::ElementResultItem(elem) => elem.count_value_or_default(),
76                // `QueryElementResultType` only emits `ElementResultItem`;
77                // the other variants belong to `QueryKeyElementPairResultType`
78                // / `QueryPathKeyElementTrioResultType` which we don't
79                // request. Defensive 0 keeps the executor total-correct
80                // even if grovedb's emission shape ever broadens.
81                _ => 0,
82            })
83            .sum();
84        Ok(vec![SplitCountEntry {
85            in_key: None,
86            key: vec![],
87            // Point-lookup executor summed verified CountTree
88            // counts to produce this; the count is explicit, hence
89            // `Some(_)` (possibly `Some(0)` if every covered branch
90            // was empty or absent).
91            count: Some(count),
92        }])
93    }
94
95    /// Generates a grovedb proof of the CountTree elements covering a
96    /// fully-covered Equal/`In` count query against a `countable: true`
97    /// index. Returns the raw proof bytes; the SDK-side
98    /// [`Self::verify_point_lookup_count_proof`] walks the proof and
99    /// extracts `count_value_or_default()` from each verified CountTree
100    /// element.
101    ///
102    /// Builds the path query via
103    /// [`Self::point_lookup_count_path_query`] (shared with the
104    /// verifier AND with [`Self::execute_no_proof`] above, so all three
105    /// sites see byte-identical path queries). Errors surface from the
106    /// builder when the query shape isn't supported — partial
107    /// coverage, more than one In, etc. — see that builder's docstring
108    /// for the exhaustive contract.
109    ///
110    /// Proof size is O(k × log n) where k is the number of covered
111    /// (Equal/In) branches and n is the tree depth: one merk path
112    /// proof per CountTree element, not per matching document.
113    /// Avoids the materialize-and-count alternative used by the
114    /// regular document-query path, which scales with the number
115    /// of matching docs and is capped at `u16::MAX`.
116    pub fn execute_point_lookup_count_with_proof(
117        &self,
118        drive: &Drive,
119        transaction: TransactionArg,
120        platform_version: &PlatformVersion,
121    ) -> Result<Vec<u8>, Error> {
122        let drive_version = &platform_version.drive;
123        let path_query = self.point_lookup_count_path_query(platform_version)?;
124        // Destructure the `CostContext` explicitly rather than calling
125        // `.unwrap()` on it: `CostContext::unwrap` is infallible (it just
126        // drops the cost field), but the visual pattern collides with
127        // `Option/Result::unwrap` and makes review noisier. Cost is
128        // discarded here because the per-mode dispatcher in
129        // `drive_dispatcher` wraps these executors with its own fee
130        // accounting.
131        let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
132            &path_query,
133            None,
134            transaction,
135            &drive_version.grove_version,
136        );
137        let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
138        Ok(proof)
139    }
140}