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::{document_count_of_element, 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 document counts
36    /// ([`document_count_of_element`]: the count, or on a
37    /// `summableOffCountIndex` index the sum). The builder handles all three structural cases
38    /// (Equal-only fully covered, In at any index position, In with
39    /// trailing Equals via `set_subquery_path`) — there's no need
40    /// for a separate recursive walker on the no-proof side.
41    pub fn execute_no_proof(
42        &self,
43        drive: &Drive,
44        transaction: TransactionArg,
45        platform_version: &PlatformVersion,
46    ) -> Result<Vec<SplitCountEntry>, Error> {
47        let drive_version = &platform_version.drive;
48        let path_query = self.point_lookup_count_path_query(platform_version)?;
49        // `grove_get_path_query` requires a `drive_operations` sink for
50        // cost accounting; the no-proof executor doesn't propagate fees
51        // upward (callers that need cost are the per-mode dispatchers
52        // in `drive_dispatcher.rs`, which wrap this for fee calculation),
53        // so we use a local vec and discard.
54        let mut drive_operations = vec![];
55        let (results, _) = drive.grove_get_path_query(
56            &path_query,
57            transaction,
58            QueryResultType::QueryElementResultType,
59            &mut drive_operations,
60            drive_version,
61        )?;
62        // Sum across emitted CountTree elements:
63        // - Equal-only: 0 or 1 element (0 when the branch is absent).
64        // - In at any position: one element per In branch that has at
65        //   least one doc; missing branches contribute 0 by virtue of
66        //   being absent from the result set.
67        // `document_count_of_element` reads `count_value_or_default()`,
68        // the `CountTree`'s count for `Element::CountTree` /
69        // `Element::SumTree` and 1 for `Element::Reference` (the
70        // unique-index-with-all-non-null case — see
71        // `Element::count_value_or_default` for the per-variant
72        // contract), except on a `summableOffCountIndex` index, whose
73        // elements' sums are its document counts. That exception is
74        // inert before protocol version 14: only meta-schema v3 admits
75        // the keyword.
76        let count: u64 = results
77            .elements
78            .iter()
79            .map(|e| match e {
80                QueryResultElement::ElementResultItem(elem) => {
81                    document_count_of_element(self.index, elem)
82                }
83                // `QueryElementResultType` only emits `ElementResultItem`;
84                // the other variants belong to `QueryKeyElementPairResultType`
85                // / `QueryPathKeyElementTrioResultType` which we don't
86                // request. Defensive 0 keeps the executor total-correct
87                // even if grovedb's emission shape ever broadens.
88                _ => 0,
89            })
90            .sum();
91        Ok(vec![SplitCountEntry {
92            in_key: None,
93            key: vec![],
94            // Point-lookup executor summed verified CountTree
95            // counts to produce this; the count is explicit, hence
96            // `Some(_)` (possibly `Some(0)` if every covered branch
97            // was empty or absent).
98            count: Some(count),
99        }])
100    }
101
102    /// Generates a grovedb proof of the CountTree elements covering a
103    /// fully-covered Equal/`In` count query against a `countable: true`
104    /// index. Returns the raw proof bytes; the SDK-side
105    /// [`Self::verify_point_lookup_count_proof`] walks the proof and
106    /// reads each verified CountTree element's document count
107    /// ([`document_count_of_element`]).
108    ///
109    /// Builds the path query via
110    /// [`Self::point_lookup_count_path_query`] (shared with the
111    /// verifier AND with [`Self::execute_no_proof`] above, so all three
112    /// sites see byte-identical path queries). Errors surface from the
113    /// builder when the query shape isn't supported — partial
114    /// coverage, more than one In, etc. — see that builder's docstring
115    /// for the exhaustive contract.
116    ///
117    /// Proof size is O(k × log n) where k is the number of covered
118    /// (Equal/In) branches and n is the tree depth: one merk path
119    /// proof per CountTree element, not per matching document.
120    /// Avoids the materialize-and-count alternative used by the
121    /// regular document-query path, which scales with the number
122    /// of matching docs and is capped at `u16::MAX`.
123    pub fn execute_point_lookup_count_with_proof(
124        &self,
125        drive: &Drive,
126        transaction: TransactionArg,
127        platform_version: &PlatformVersion,
128    ) -> Result<Vec<u8>, Error> {
129        let drive_version = &platform_version.drive;
130        let path_query = self.point_lookup_count_path_query(platform_version)?;
131        // Destructure the `CostContext` explicitly rather than calling
132        // `.unwrap()` on it: `CostContext::unwrap` is infallible (it just
133        // drops the cost field), but the visual pattern collides with
134        // `Option/Result::unwrap` and makes review noisier. Cost is
135        // discarded here because the per-mode dispatcher in
136        // `drive_dispatcher` wraps these executors with its own fee
137        // accounting.
138        let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
139            &path_query,
140            None,
141            transaction,
142            &drive_version.grove_version,
143        );
144        let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
145        Ok(proof)
146    }
147}