drive/query/drive_document_sum_query/execute_range_sum.rs
1//! Range execution paths for the sum query. Parallels count's
2//! `execute_range_count.rs`.
3//!
4//! - [`DriveDocumentSumQuery::execute_range_sum_no_proof`] — Rust-side
5//! walk via `query_aggregate_sum` (or per-In fan-out for compound
6//! shapes), returning a single `Aggregate` entry or per-(in_key, key)
7//! distinct entries without a proof.
8//! - [`DriveDocumentSumQuery::execute_aggregate_sum_with_proof`] —
9//! grovedb `AggregateSumOnRange` proof, returning a single i64
10//! verified out of the proof.
11//! - [`DriveDocumentSumQuery::execute_distinct_sum_with_proof`] —
12//! regular range proof against the `ProvableSumTree`, returning
13//! per-key `KVSum` ops bound to the merk root.
14
15use super::{DriveDocumentSumQuery, RangeSumOptions, RangeSumWalkMode, SumEntry};
16use crate::drive::Drive;
17use crate::error::drive::DriveError;
18use crate::error::query::QuerySyntaxError;
19use crate::error::Error;
20use crate::query::{WhereClause, WhereOperator};
21use dpp::data_contract::document_type::methods::DocumentTypeV0Methods;
22use dpp::version::PlatformVersion;
23use grovedb::query_result_type::QueryResultType;
24use grovedb::TransactionArg;
25use grovedb_costs::CostContext;
26
27impl DriveDocumentSumQuery<'_> {
28 /// Range-aware sum walk against a `rangeSummable: true` index.
29 ///
30 /// Mirror of count's `execute_range_count_no_proof`. Routing:
31 /// - **Flat summed** (no `In`, distinct=false): single
32 /// `query_aggregate_sum` call against the merk-level
33 /// `AggregateSumOnRange` primitive. O(log n).
34 /// - **Compound summed** (`In` on prefix, distinct=false): per-In
35 /// fan-out — one `query_aggregate_sum` call per matched In
36 /// branch, summed in Rust.
37 /// - **Distinct mode** (`distinct=true`): walks the unified
38 /// `distinct_sum_path_query` and emits one entry per matched
39 /// `(in_key, key)` pair. (Currently stubbed pending the
40 /// distinct-builder port.)
41 pub fn execute_range_sum_no_proof(
42 &self,
43 drive: &Drive,
44 options: &RangeSumOptions,
45 transaction: TransactionArg,
46 platform_version: &PlatformVersion,
47 ) -> Result<Vec<SumEntry>, Error> {
48 let drive_version = &platform_version.drive;
49 let has_in_on_prefix = self
50 .where_clauses
51 .iter()
52 .any(|wc| wc.operator == WhereOperator::In);
53
54 if matches!(options.walk_mode, RangeSumWalkMode::Aggregate) {
55 if has_in_on_prefix {
56 // Enforce exactly one `In` clause. Without this, a request
57 // with multiple In filters would silently use only the
58 // first and drop the rest, producing an over-broad total.
59 let in_clauses: Vec<&WhereClause> = self
60 .where_clauses
61 .iter()
62 .filter(|wc| wc.operator == WhereOperator::In)
63 .collect();
64 if in_clauses.len() != 1 {
65 return Err(Error::Query(
66 QuerySyntaxError::InvalidWhereClauseComponents(
67 "compound summed range sum path requires exactly one `in` clause",
68 ),
69 ));
70 }
71 let in_clause = in_clauses[0];
72 let in_values = in_clause.in_values().into_data_with_error()??;
73 let other_clauses: Vec<WhereClause> = self
74 .where_clauses
75 .iter()
76 .filter(|wc| wc.operator != WhereOperator::In)
77 .cloned()
78 .collect();
79
80 let mut total: i64 = 0;
81 let mut seen_keys: std::collections::BTreeSet<Vec<u8>> =
82 std::collections::BTreeSet::new();
83 for value in in_values.iter() {
84 let key_bytes = self.document_type.serialize_value_for_key(
85 in_clause.field.as_str(),
86 value,
87 platform_version,
88 )?;
89 if !seen_keys.insert(key_bytes) {
90 continue;
91 }
92
93 let mut clauses_for_value = other_clauses.clone();
94 clauses_for_value.push(WhereClause {
95 field: in_clause.field.clone(),
96 operator: WhereOperator::Equal,
97 value: value.clone(),
98 });
99 let per_value_query = DriveDocumentSumQuery {
100 document_type: self.document_type,
101 contract_id: self.contract_id,
102 document_type_name: self.document_type_name.clone(),
103 index: self.index,
104 where_clauses: clauses_for_value,
105 sum_property: self.sum_property.clone(),
106 };
107 let path_query = per_value_query.aggregate_sum_path_query(platform_version)?;
108 let CostContext { value, cost: _ } = drive.grove.query_aggregate_sum(
109 &path_query,
110 transaction,
111 &drive_version.grove_version,
112 );
113 let sum = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
114 // Use `checked_add` rather than `saturating_add` so an
115 // overflowed aggregate fails deterministically instead
116 // of silently clamping at i64::MAX. The proof-side
117 // verifier sees the same overflow at the same point
118 // (the grovedb primitive itself returns i64), so
119 // refusing here keeps prover and verifier in sync
120 // on the rejection rather than letting the no-proof
121 // path return a value the proof path would reject.
122 total = total.checked_add(sum).ok_or_else(|| {
123 Error::Query(QuerySyntaxError::Unsupported(
124 "compound In-on-prefix range-sum overflowed i64 when summing \
125 per-In aggregates. Narrow the query (smaller In set, narrower \
126 range) or use multiple queries and combine client-side."
127 .to_string(),
128 ))
129 })?;
130 }
131 return Ok(vec![SumEntry {
132 in_key: None,
133 key: Vec::new(),
134 sum: Some(total),
135 }]);
136 }
137 // Flat summed (no In on prefix): single aggregate read.
138 let path_query = self.aggregate_sum_path_query(platform_version)?;
139 let CostContext { value, cost: _ } = drive.grove.query_aggregate_sum(
140 &path_query,
141 transaction,
142 &drive_version.grove_version,
143 );
144 let sum = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
145 return Ok(vec![SumEntry {
146 in_key: None,
147 key: Vec::new(),
148 sum: Some(sum),
149 }]);
150 }
151
152 let RangeSumWalkMode::Distinct(distinct_limit) = options.walk_mode else {
153 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
154 "aggregate range sums must return before the distinct storage walk",
155 )));
156 };
157 let path_query = self.distinct_sum_path_query(
158 Some(distinct_limit),
159 options.left_to_right,
160 platform_version,
161 )?;
162 let base_path_len = path_query.path.len();
163
164 let mut drive_operations = vec![];
165 let result = drive.grove_get_raw_path_query(
166 &path_query,
167 transaction,
168 QueryResultType::QueryPathKeyElementTrioResultType,
169 &mut drive_operations,
170 drive_version,
171 );
172 let elements = match result {
173 Ok((elements, _)) => elements,
174 Err(Error::GroveDB(e))
175 if matches!(
176 e.as_ref(),
177 grovedb::Error::PathNotFound(_)
178 | grovedb::Error::PathParentLayerNotFound(_)
179 | grovedb::Error::PathKeyNotFound(_)
180 ) =>
181 {
182 return Ok(Vec::new());
183 }
184 Err(e) => return Err(e),
185 };
186
187 let mut entries: Vec<SumEntry> = Vec::new();
188 for triple in elements.to_path_key_elements() {
189 let (path, key, element) = triple;
190 let sum = element.sum_value_or_default();
191 if sum == 0 {
192 continue;
193 }
194 let in_key = if has_in_on_prefix && path.len() > base_path_len {
195 Some(path[base_path_len].clone())
196 } else {
197 None
198 };
199 entries.push(SumEntry {
200 in_key,
201 key,
202 sum: Some(sum),
203 });
204 }
205
206 Ok(entries)
207 }
208
209 /// Generates a grovedb `AggregateSumOnRange` proof for a range-sum
210 /// query against a `rangeSummable` index. Returned proof bytes
211 /// verify via `GroveDb::verify_aggregate_sum_query` yielding
212 /// `(root_hash, i64 sum)`.
213 pub fn execute_aggregate_sum_with_proof(
214 &self,
215 drive: &Drive,
216 transaction: TransactionArg,
217 platform_version: &PlatformVersion,
218 ) -> Result<Vec<u8>, Error> {
219 let drive_version = &platform_version.drive;
220 let path_query = self.aggregate_sum_path_query(platform_version)?;
221 let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
222 &path_query,
223 None,
224 transaction,
225 &drive_version.grove_version,
226 );
227 let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
228 Ok(proof)
229 }
230
231 /// Per-distinct-key range-sum proof against this query's
232 /// `rangeSummable` index. Mirror of count's
233 /// `execute_distinct_count_with_proof`. Currently routes through
234 /// `distinct_sum_path_query` which is stubbed (pending the
235 /// ~280-line port from count); calls before that lands surface
236 /// `Unsupported` cleanly.
237 pub fn execute_distinct_sum_with_proof(
238 &self,
239 drive: &Drive,
240 limit: u16,
241 left_to_right: bool,
242 transaction: TransactionArg,
243 platform_version: &PlatformVersion,
244 ) -> Result<Vec<u8>, Error> {
245 let drive_version = &platform_version.drive;
246 let path_query =
247 self.distinct_sum_path_query(Some(limit), left_to_right, platform_version)?;
248 let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
249 &path_query,
250 None,
251 transaction,
252 &drive_version.grove_version,
253 );
254 let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
255 Ok(proof)
256 }
257
258 /// Generates a grovedb leaf-PCPS `AggregateCountAndSumOnRange`
259 /// proof for a combined count + sum range query against an index
260 /// that declares BOTH `rangeCountable: true` AND `rangeSummable:
261 /// true`. Returned proof bytes verify via
262 /// `GroveDb::verify_aggregate_count_and_sum_query` yielding
263 /// `(root_hash, u64 count, i64 sum)` — the load-bearing primitive
264 /// for the [average-index-examples chapter]
265 /// (../../../../book/src/drive/average-index-examples.md)'s
266 /// Query 5 ("Class Trend"). PCPS-only: the terminator's value tree
267 /// MUST be a `ProvableCountProvableSumTree`; lighter
268 /// (CountSumTree / ProvableCountSumTree / ProvableSumTree)
269 /// terminators are rejected at the grovedb merk-gate.
270 ///
271 /// Leaf analog of
272 /// [`Self::execute_carrier_aggregate_count_and_sum_with_proof`]:
273 /// same primitive, no outer `In` fan-out — single
274 /// `(count, sum)` per proof rather than per-In-key `(count, sum)`
275 /// triples.
276 pub fn execute_aggregate_count_and_sum_with_proof(
277 &self,
278 drive: &Drive,
279 transaction: TransactionArg,
280 platform_version: &PlatformVersion,
281 ) -> Result<Vec<u8>, Error> {
282 let drive_version = &platform_version.drive;
283 let path_query = self.aggregate_count_and_sum_path_query(platform_version)?;
284 let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
285 &path_query,
286 None,
287 transaction,
288 &drive_version.grove_version,
289 );
290 let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
291 Ok(proof)
292 }
293
294 /// Generates a grovedb **carrier** `AggregateSumOnRange` proof
295 /// for `In + range` queries with `group_by = [in_field]` (and the
296 /// `RangeAggregateCarrierProof` mode in general). Sum analog of
297 /// count's
298 /// [`crate::query::drive_document_count_query::DriveDocumentCountQuery::execute_carrier_aggregate_count_with_proof`].
299 ///
300 /// Builds the carrier `PathQuery` via
301 /// [`Self::carrier_aggregate_sum_path_query`] and asks grovedb
302 /// for a proof. The proof commits one aggregate sum per resolved
303 /// In branch; verified client-side via
304 /// `GroveDb::verify_aggregate_sum_query_per_key` (grovedb PR #670
305 /// head `e98bab5f`), which returns `(RootHash, Vec<(Vec<u8>, i64)>)`.
306 ///
307 /// `left_to_right` and `limit` are byte-load-bearing — they are
308 /// part of the `PathQuery` bytes the verifier rebuilds. See count's
309 /// analog for the rationale.
310 pub fn execute_carrier_aggregate_sum_with_proof(
311 &self,
312 drive: &Drive,
313 limit: Option<u16>,
314 left_to_right: bool,
315 transaction: TransactionArg,
316 platform_version: &PlatformVersion,
317 ) -> Result<Vec<u8>, Error> {
318 let drive_version = &platform_version.drive;
319 let path_query =
320 self.carrier_aggregate_sum_path_query(limit, left_to_right, platform_version)?;
321 let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
322 &path_query,
323 None,
324 transaction,
325 &drive_version.grove_version,
326 );
327 let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
328 Ok(proof)
329 }
330
331 /// Combined PCPS carrier proof:
332 /// `AggregateCountAndSumOnRange`-on-carrier. Sum-and-count analog
333 /// of [`Self::execute_carrier_aggregate_sum_with_proof`]. Requires
334 /// the chosen index to declare BOTH `rangeCountable: true` AND
335 /// `rangeSummable: true` so the terminator's value tree is a
336 /// `ProvableCountProvableSumTree`.
337 ///
338 /// Returns proof bytes the verifier maps to
339 /// `Vec<(Vec<u8>, u64, i64)>` via
340 /// `GroveDb::verify_aggregate_count_and_sum_query_per_key` (grovedb
341 /// PR #670 head `e98bab5f`) — one `(in_key, count, sum)` triple
342 /// per resolved In branch.
343 pub fn execute_carrier_aggregate_count_and_sum_with_proof(
344 &self,
345 drive: &Drive,
346 limit: Option<u16>,
347 left_to_right: bool,
348 transaction: TransactionArg,
349 platform_version: &PlatformVersion,
350 ) -> Result<Vec<u8>, Error> {
351 let drive_version = &platform_version.drive;
352 let path_query = self.carrier_aggregate_count_and_sum_path_query(
353 limit,
354 left_to_right,
355 platform_version,
356 )?;
357 let CostContext { value, cost: _ } = drive.grove.get_proved_path_query(
358 &path_query,
359 None,
360 transaction,
361 &drive_version.grove_version,
362 );
363 let proof = value.map_err(|e| Error::GroveDB(Box::new(e)))?;
364 Ok(proof)
365 }
366}