drive/query/drive_document_sum_query/drive_dispatcher.rs
1//! Sum-query dispatcher entry point.
2//!
3//! Parallels [`crate::query::drive_document_count_query::drive_dispatcher`]
4//! for the sum surface. Routes a parsed [`DocumentSumRequest`] to one of
5//! the per-mode executors based on the (where × mode × prove) triple,
6//! exactly the way count's dispatcher does.
7//!
8//! `where_clauses_from_value` / `order_clauses_from_value` are wire-shape
9//! adapters that the bench and the gRPC handler both use to convert the
10//! CBOR-decoded `Value::Array` input into structured `Vec<WhereClause>` /
11//! `Vec<OrderClause>`. Identical input contract to count.
12
13use crate::drive::Drive;
14use crate::error::Error;
15use crate::query::drive_document_sum_query::{
16 DocumentSumMode, DocumentSumRequest, DocumentSumResponse, RangeSumOptions, SumMode,
17};
18use crate::query::{OrderClause, WhereClause};
19use dpp::data_contract::accessors::v0::DataContractV0Getters;
20use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
21use dpp::platform_value::Value;
22use dpp::version::PlatformVersion;
23use grovedb::TransactionArg;
24
25#[cfg(feature = "server")]
26impl Drive {
27 /// Server-side entry point for the sum surface. Routes a
28 /// [`DocumentSumRequest`] to the appropriate executor based on the
29 /// where-shape, requested mode, and `prove` flag.
30 ///
31 /// Mirrors [`Drive::execute_document_count_request`].
32 pub fn execute_document_sum_request(
33 &self,
34 request: DocumentSumRequest,
35 transaction: TransactionArg,
36 platform_version: &PlatformVersion,
37 ) -> Result<DocumentSumResponse, Error> {
38 let resolved_mode = super::mode_detection::detect_sum_mode(&request, platform_version)?;
39
40 let contract_id = request.contract.id().to_buffer();
41 let document_type_name = request.document_type.name().to_string();
42 let where_clauses = request.where_clauses;
43 let sum_property = request.sum_property;
44 // Default direction is ascending; the first order clause's
45 // direction (if any) wins. Mirrors count's analog.
46 let order_by_ascending = request
47 .order_clauses
48 .first()
49 .map(|c| c.ascending)
50 .unwrap_or(true);
51
52 match resolved_mode {
53 DocumentSumMode::Total => {
54 let entries = self.execute_document_sum_total_no_proof(
55 contract_id,
56 request.document_type,
57 document_type_name,
58 where_clauses,
59 sum_property,
60 transaction,
61 platform_version,
62 )?;
63 let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
64 Ok(DocumentSumResponse::Aggregate(total))
65 }
66 DocumentSumMode::PerInValue => {
67 let options = RangeSumOptions {
68 return_distinct_sums_in_range: false,
69 carrier_outer_limit: None,
70 left_to_right: order_by_ascending,
71 };
72 Ok(DocumentSumResponse::Entries(
73 self.execute_document_sum_per_in_value_no_proof(
74 contract_id,
75 request.document_type,
76 document_type_name,
77 where_clauses,
78 sum_property,
79 options,
80 transaction,
81 platform_version,
82 )?,
83 ))
84 }
85 DocumentSumMode::RangeNoProof => {
86 let return_distinct = matches!(
87 request.mode,
88 SumMode::GroupByRange | SumMode::GroupByCompound
89 );
90 let options = RangeSumOptions {
91 return_distinct_sums_in_range: return_distinct,
92 carrier_outer_limit: None,
93 left_to_right: order_by_ascending,
94 };
95 let entries = self.execute_document_sum_range_no_proof(
96 contract_id,
97 request.document_type,
98 document_type_name,
99 where_clauses,
100 sum_property,
101 options,
102 transaction,
103 platform_version,
104 )?;
105 if matches!(request.mode, SumMode::Aggregate) {
106 let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
107 Ok(DocumentSumResponse::Aggregate(total))
108 } else {
109 Ok(DocumentSumResponse::Entries(entries))
110 }
111 }
112 DocumentSumMode::RangeProof => Ok(DocumentSumResponse::Proof(
113 self.execute_document_sum_range_proof(
114 contract_id,
115 request.document_type,
116 document_type_name,
117 where_clauses,
118 sum_property,
119 transaction,
120 platform_version,
121 )?,
122 )),
123 DocumentSumMode::RangeDistinctProof => {
124 // Validate-don't-clamp limit policy on the prove path:
125 // client-side proof reconstruction needs the EXACT
126 // limit value the server applied to the path query
127 // (the SDK rebuilds the same `SizedQuery::limit` for
128 // merk-root recomputation). Silent clamping or a
129 // tuned `default_query_limit` would byte-differ the
130 // reconstructed path query and break verification.
131 //
132 // Limit fallback uses [`crate::config::DEFAULT_QUERY_LIMIT`]
133 // (compile-time constant), NOT
134 // `drive_config.default_query_limit` (operator-tunable
135 // runtime value). `max_query_limit` still gates the
136 // request as a DoS-protection knob — proofs never
137 // cross the operator-set ceiling, but the ceiling
138 // itself doesn't shape proof bytes; it only decides
139 // whether the request gets served.
140 //
141 // Mirrors count's policy at
142 // `drive_document_count_query::drive_dispatcher`
143 // `DocumentCountMode::RangeDistinctProof`.
144 let effective_limit = request
145 .limit
146 .unwrap_or(crate::config::DEFAULT_QUERY_LIMIT as u32);
147 if effective_limit > request.drive_config.max_query_limit as u32 {
148 return Err(Error::Query(
149 crate::error::query::QuerySyntaxError::InvalidLimit(format!(
150 "limit {} exceeds max_query_limit {} on the prove + \
151 distinct-walk path (GROUP BY a range field, SUM); \
152 reduce the requested limit or use prove = false",
153 effective_limit, request.drive_config.max_query_limit
154 )),
155 ));
156 }
157 let limit_u16 = u16::try_from(effective_limit).map_err(|_| {
158 Error::Query(crate::error::query::QuerySyntaxError::Unsupported(format!(
159 "limit {} exceeds u16::MAX for range-distinct sum proof",
160 effective_limit
161 )))
162 })?;
163 Ok(DocumentSumResponse::Proof(
164 self.execute_document_sum_range_distinct_proof(
165 contract_id,
166 request.document_type,
167 document_type_name,
168 where_clauses,
169 sum_property,
170 limit_u16,
171 order_by_ascending,
172 transaction,
173 platform_version,
174 )?,
175 ))
176 }
177 DocumentSumMode::PointLookupProof => Ok(DocumentSumResponse::Proof(
178 self.execute_document_sum_point_lookup_proof(
179 contract_id,
180 request.document_type,
181 document_type_name,
182 where_clauses,
183 sum_property,
184 transaction,
185 platform_version,
186 )?,
187 )),
188 DocumentSumMode::RangeAggregateCarrierProof => {
189 // Validate-don't-clamp limit policy on the prove path
190 // — same contract as RangeDistinctProof above. The
191 // carrier proof's outer-walk cap is `SizedQuery::limit`
192 // bytes-of-proof material; a silent clamp would
193 // byte-differ the SDK's reconstruction and break
194 // verification. Unlike the distinct arm, the carrier
195 // arm passes `Option<u16>` (None = unbounded outer
196 // walk), so the request's `None` stays `None` instead
197 // of falling back to a default.
198 let limit_u16 = request
199 .limit
200 .map(|l| {
201 if l > request.drive_config.max_query_limit as u32 {
202 return Err(Error::Query(
203 crate::error::query::QuerySyntaxError::InvalidLimit(format!(
204 "limit {} exceeds max_query_limit {} on the prove + \
205 carrier-aggregate path (GROUP BY In + range, SUM); \
206 reduce the requested limit or use prove = false",
207 l, request.drive_config.max_query_limit
208 )),
209 ));
210 }
211 u16::try_from(l).map_err(|_| {
212 Error::Query(crate::error::query::QuerySyntaxError::Unsupported(
213 format!(
214 "limit {} exceeds u16::MAX for carrier-aggregate sum proof",
215 l
216 ),
217 ))
218 })
219 })
220 .transpose()?;
221 Ok(DocumentSumResponse::Proof(
222 self.execute_document_sum_range_aggregate_carrier_proof(
223 contract_id,
224 request.document_type,
225 document_type_name,
226 where_clauses,
227 sum_property,
228 limit_u16,
229 order_by_ascending,
230 transaction,
231 platform_version,
232 )?,
233 ))
234 }
235 }
236 }
237}
238
239// `detect_sum_mode` lives in the versioned
240// [`mode_detection`](super::mode_detection) module — the routing
241// table is consensus-relevant on the query surface and protocol
242// versions that change it must do so behind a method-version bump.
243
244/// Parse the wire-CBOR `Value::Array` shape into structured
245/// `Vec<WhereClause>`. Delegates to count's parser.
246pub fn where_clauses_from_value(value: &Value) -> Result<Vec<WhereClause>, Error> {
247 crate::query::drive_document_count_query::drive_dispatcher::where_clauses_from_value(value)
248}
249
250/// Parse the wire-CBOR `Value::Array` shape into structured
251/// `Vec<OrderClause>`. Delegates to count's parser.
252pub fn order_clauses_from_value(value: &Value) -> Result<Vec<OrderClause>, Error> {
253 crate::query::drive_document_count_query::drive_dispatcher::order_clauses_from_value(value)
254}