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}