drive/query/drive_document_sum_query/
drive_dispatcher.rs1use crate::config::DriveConfig;
14use crate::drive::Drive;
15use crate::error::query::QuerySyntaxError;
16use crate::error::Error;
17use crate::query::drive_document_sum_query::{
18 DocumentSumMode, DocumentSumRequest, DocumentSumResponse, RangeSumOptions, RangeSumWalkMode,
19 SumMode,
20};
21use crate::query::{
22 validate_and_canonicalize_where_clauses, validate_resolved_time_range_clause_shapes,
23 OrderClause, WhereClause,
24};
25use dpp::data_contract::accessors::v0::DataContractV0Getters;
26use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
27use dpp::platform_value::Value;
28use dpp::version::PlatformVersion;
29use grovedb::TransactionArg;
30
31fn effective_no_proof_distinct_limit(
32 requested_limit: Option<u32>,
33 drive_config: &DriveConfig,
34) -> Result<u16, Error> {
35 let effective_limit = requested_limit
36 .unwrap_or(drive_config.default_query_limit as u32)
37 .min(drive_config.max_query_limit as u32);
38
39 if effective_limit == 0 {
40 return Err(Error::Query(QuerySyntaxError::InvalidLimit(
41 "effective distinct SUM limit must be greater than zero".to_string(),
42 )));
43 }
44
45 Ok(effective_limit as u16)
48}
49
50#[cfg(feature = "server")]
51impl Drive {
52 pub fn execute_document_sum_request(
58 &self,
59 mut request: DocumentSumRequest,
60 transaction: TransactionArg,
61 platform_version: &PlatformVersion,
62 ) -> Result<DocumentSumResponse, Error> {
63 request.where_clauses =
68 validate_and_canonicalize_where_clauses(request.where_clauses, platform_version)?;
69 validate_resolved_time_range_clause_shapes(
72 &request.where_clauses,
73 &request.resolved_time_ranges,
74 )?;
75 let resolved_mode = super::mode_detection::detect_sum_mode(&request, platform_version)?;
76
77 let contract_id = request.contract.id().to_buffer();
78 let document_type_name = request.document_type.name().to_string();
79 let where_clauses = request.where_clauses;
80 let resolved_time_ranges = request.resolved_time_ranges;
81 let sum_property = request.sum_property;
82 let order_by_ascending = request
85 .order_clauses
86 .first()
87 .map(|c| c.ascending)
88 .unwrap_or(true);
89
90 match resolved_mode {
91 DocumentSumMode::Total => {
92 let entries = self.execute_document_sum_total_no_proof(
93 contract_id,
94 request.document_type,
95 document_type_name,
96 where_clauses,
97 &resolved_time_ranges,
98 sum_property,
99 transaction,
100 platform_version,
101 )?;
102 let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
103 Ok(DocumentSumResponse::Aggregate(total))
104 }
105 DocumentSumMode::PerInValue => {
106 let options = RangeSumOptions {
107 walk_mode: RangeSumWalkMode::Aggregate,
108 carrier_outer_limit: None,
109 left_to_right: order_by_ascending,
110 };
111 Ok(DocumentSumResponse::Entries(
112 self.execute_document_sum_per_in_value_no_proof(
113 contract_id,
114 request.document_type,
115 document_type_name,
116 where_clauses,
117 &resolved_time_ranges,
118 sum_property,
119 options,
120 transaction,
121 platform_version,
122 )?,
123 ))
124 }
125 DocumentSumMode::RangeNoProof => {
126 let return_distinct = matches!(
127 request.mode,
128 SumMode::GroupByRange | SumMode::GroupByCompound
129 );
130 let walk_mode = if return_distinct {
131 RangeSumWalkMode::Distinct(effective_no_proof_distinct_limit(
132 request.limit,
133 request.drive_config,
134 )?)
135 } else {
136 RangeSumWalkMode::Aggregate
137 };
138 let options = RangeSumOptions {
139 walk_mode,
140 carrier_outer_limit: None,
141 left_to_right: order_by_ascending,
142 };
143 let entries = self.execute_document_sum_range_no_proof(
144 contract_id,
145 request.document_type,
146 document_type_name,
147 where_clauses,
148 &resolved_time_ranges,
149 sum_property,
150 options,
151 transaction,
152 platform_version,
153 )?;
154 if matches!(request.mode, SumMode::Aggregate) {
155 let total = entries.first().and_then(|e| e.sum).unwrap_or(0);
156 Ok(DocumentSumResponse::Aggregate(total))
157 } else {
158 Ok(DocumentSumResponse::Entries(entries))
159 }
160 }
161 DocumentSumMode::RangeProof => Ok(DocumentSumResponse::Proof(
162 self.execute_document_sum_range_proof(
163 contract_id,
164 request.document_type,
165 document_type_name,
166 where_clauses,
167 &resolved_time_ranges,
168 sum_property,
169 transaction,
170 platform_version,
171 )?,
172 )),
173 DocumentSumMode::RangeDistinctProof => {
174 let effective_limit = request
195 .limit
196 .unwrap_or(crate::config::DEFAULT_QUERY_LIMIT as u32);
197 if effective_limit > request.drive_config.max_query_limit as u32 {
198 return Err(Error::Query(
199 crate::error::query::QuerySyntaxError::InvalidLimit(format!(
200 "limit {} exceeds max_query_limit {} on the prove + \
201 distinct-walk path (GROUP BY a range field, SUM); \
202 reduce the requested limit or use prove = false",
203 effective_limit, request.drive_config.max_query_limit
204 )),
205 ));
206 }
207 let limit_u16 = u16::try_from(effective_limit).map_err(|_| {
208 Error::Query(crate::error::query::QuerySyntaxError::Unsupported(format!(
209 "limit {} exceeds u16::MAX for range-distinct sum proof",
210 effective_limit
211 )))
212 })?;
213 Ok(DocumentSumResponse::Proof(
214 self.execute_document_sum_range_distinct_proof(
215 contract_id,
216 request.document_type,
217 document_type_name,
218 where_clauses,
219 &resolved_time_ranges,
220 sum_property,
221 limit_u16,
222 order_by_ascending,
223 transaction,
224 platform_version,
225 )?,
226 ))
227 }
228 DocumentSumMode::PointLookupProof => Ok(DocumentSumResponse::Proof(
229 self.execute_document_sum_point_lookup_proof(
230 contract_id,
231 request.document_type,
232 document_type_name,
233 where_clauses,
234 &resolved_time_ranges,
235 sum_property,
236 transaction,
237 platform_version,
238 )?,
239 )),
240 DocumentSumMode::RangeAggregateCarrierProof => {
241 let limit_u16 = request
251 .limit
252 .map(|l| {
253 if l > request.drive_config.max_query_limit as u32 {
254 return Err(Error::Query(
255 crate::error::query::QuerySyntaxError::InvalidLimit(format!(
256 "limit {} exceeds max_query_limit {} on the prove + \
257 carrier-aggregate path (GROUP BY In + range, SUM); \
258 reduce the requested limit or use prove = false",
259 l, request.drive_config.max_query_limit
260 )),
261 ));
262 }
263 u16::try_from(l).map_err(|_| {
264 Error::Query(crate::error::query::QuerySyntaxError::Unsupported(
265 format!(
266 "limit {} exceeds u16::MAX for carrier-aggregate sum proof",
267 l
268 ),
269 ))
270 })
271 })
272 .transpose()?;
273 Ok(DocumentSumResponse::Proof(
274 self.execute_document_sum_range_aggregate_carrier_proof(
275 contract_id,
276 request.document_type,
277 document_type_name,
278 where_clauses,
279 &resolved_time_ranges,
280 sum_property,
281 limit_u16,
282 order_by_ascending,
283 transaction,
284 platform_version,
285 )?,
286 ))
287 }
288 }
289 }
290}
291
292pub fn where_clauses_from_value(
300 value: &Value,
301 platform_version: &PlatformVersion,
302) -> Result<Vec<WhereClause>, Error> {
303 crate::query::drive_document_count_query::drive_dispatcher::where_clauses_from_value(
304 value,
305 platform_version,
306 )
307}
308
309pub fn order_clauses_from_value(value: &Value) -> Result<Vec<OrderClause>, Error> {
312 crate::query::drive_document_count_query::drive_dispatcher::order_clauses_from_value(value)
313}
314
315#[cfg(test)]
316mod tests {
317 use super::effective_no_proof_distinct_limit;
318 use crate::config::DriveConfig;
319
320 #[test]
321 fn no_proof_distinct_limit_uses_the_default_and_clamps_to_the_maximum() {
322 let config = DriveConfig {
323 default_query_limit: 25,
324 max_query_limit: 100,
325 ..DriveConfig::default()
326 };
327
328 assert_eq!(
329 effective_no_proof_distinct_limit(None, &config).unwrap(),
330 25
331 );
332 assert_eq!(
333 effective_no_proof_distinct_limit(Some(7), &config).unwrap(),
334 7
335 );
336 assert_eq!(
337 effective_no_proof_distinct_limit(Some(10_000), &config).unwrap(),
338 100
339 );
340
341 let disabled = DriveConfig {
342 max_query_limit: 0,
343 ..config
344 };
345 assert!(effective_no_proof_distinct_limit(None, &disabled).is_err());
346 }
347}