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}